Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 318 – Dekoratoren für Funktionen und Methoden

Autor:
Kevin D. Smith <Kevin.Smith at theMorgue.org>, Jim J. Jewett, Skip Montanaro, Anthony Baxter
Status:
Final
Typ:
Standards Track
Erstellt:
05-Jun-2003
Python-Version:
2.4
Post-History:
09-Jun-2003, 10-Jun-2003, 27-Feb-2004, 23-Mar-2004, 30-Aug-2004, 02-Sep-2004

Inhaltsverzeichnis

WarnungWarnungWarnung

Dieses Dokument soll die Dekorator-Syntax und den Prozess beschreiben, der zu den getroffenen Entscheidungen geführt hat. Es versucht nicht, die riesige Anzahl potenzieller alternativer Syntaxen abzudecken, noch ist es ein Versuch, alle positiven und negativen Aspekte jeder Form erschöpfend aufzulisten.

Zusammenfassung

Die aktuelle Methode zur Transformation von Funktionen und Methoden (z. B. Deklarieren als Klassen- oder statische Methode) ist umständlich und kann zu schwer verständlichem Code führen. Idealerweise sollten diese Transformationen an derselben Stelle im Code vorgenommen werden, an der die Deklaration selbst erfolgt. Dieses PEP führt eine neue Syntax für Transformationen einer Funktions- oder Methodendeklaration ein.

Motivation

Die aktuelle Methode zur Anwendung einer Transformation auf eine Funktion oder Methode platziert die eigentliche Transformation nach dem Funktionskörper. Bei großen Funktionen trennt dies eine Schlüsselkomponente des Verhaltens der Funktion von der Definition der restlichen externen Schnittstelle der Funktion. Zum Beispiel

def foo(self):
    perform method operation
foo = classmethod(foo)

Dies wird bei längeren Methoden weniger lesbar. Es erscheint auch weniger Python-typisch, die Funktion dreimal für etwas zu benennen, das konzeptionell eine einzige Deklaration ist. Eine Lösung für dieses Problem ist, die Transformation der Methode näher an die Deklaration der Methode selbst zu verschieben. Die Absicht der neuen Syntax ist es, zu ersetzen

def foo(cls):
    pass
foo = synchronized(lock)(foo)
foo = classmethod(foo)

mit einer Alternative, die die Dekoration in der Funktionsdeklaration platziert

@classmethod
@synchronized(lock)
def foo(cls):
    pass

Auch Modifikationen von Klassen auf diese Weise sind möglich, obwohl die Vorteile nicht so unmittelbar ersichtlich sind. Fast sicher könnte alles, was mit Klassendekoratoren getan werden könnte, mit Metaklassen getan werden, aber die Verwendung von Metaklassen ist ausreichend obskur, dass es eine gewisse Anziehungskraft hat, einen einfacheren Weg zu haben, einfache Modifikationen an Klassen vorzunehmen. Für Python 2.4 werden nur Funktions-/Methodendekoratoren hinzugefügt.

PEP 3129 schlägt die Hinzufügung von Klassendekoratoren ab Python 2.6 vor.

Warum ist das so schwer?

Zwei Dekoratoren (classmethod() und staticmethod()) sind seit Version 2.2 in Python verfügbar. Seit etwa dieser Zeit wird angenommen, dass einige syntaktische Unterstützung dafür irgendwann in die Sprache aufgenommen wird. Angesichts dieser Annahme könnte man sich fragen, warum es so schwierig war, einen Konsens zu erzielen. Diskussionen fanden zeitweise sowohl in comp.lang.python als auch in der python-dev Mailingliste statt, wie Funktionsdekreatoren am besten implementiert werden könnten. Es gibt keinen einzelnen klaren Grund, warum dies so sein sollte, aber einige Probleme scheinen am spaltendsten zu sein.

  • Uneinigkeit darüber, wo die „Erklärungsabsicht“ hingehört. Fast jeder stimmt zu, dass das Dekorieren/Transformieren einer Funktion am Ende ihrer Definition suboptimal ist. Darüber hinaus scheint es keinen klaren Konsens darüber zu geben, wo diese Informationen platziert werden sollen.
  • Syntaktische Einschränkungen. Python ist eine syntaktisch einfache Sprache mit ziemlich starken Einschränkungen, was ohne „Probleme“ (sowohl visuell als auch in Bezug auf den Sprachparser) getan werden kann und was nicht. Es gibt keine offensichtliche Möglichkeit, diese Informationen so zu strukturieren, dass Personen, die mit dem Konzept nicht vertraut sind, denken: „Oh ja, ich weiß, was du tust.“ Das Beste, was möglich zu sein scheint, ist, neue Benutzer daran zu hindern, ein wild falsches mentales Modell davon zu erstellen, was die Syntax bedeutet.
  • Allgemeine Unvertrautheit mit dem Konzept. Für Leute, die eine beiläufige Bekanntschaft mit Algebra (oder sogar grundlegender Arithmetik) haben oder mindestens eine andere Programmiersprache verwendet haben, ist vieles an Python intuitiv. Nur sehr wenige Leute werden Erfahrungen mit dem Dekorator-Konzept haben, bevor sie ihm in Python begegnen. Es gibt einfach kein starkes vorgeprägtes Meme, das das Konzept erfasst.
  • Syntaxdiskussionen im Allgemeinen scheinen mehr Streitigkeiten zu verursachen als fast alles andere. Die Leser werden auf die Diskussionen über den ternären Operator verwiesen, die mit PEP 308 für ein weiteres Beispiel dieser Art verbunden waren.

Hintergrund

Es gibt allgemeine Zustimmung, dass syntaktische Unterstützung wünschenswert ist, um den aktuellen Stand der Dinge zu verbessern. Guido erwähnte syntaktische Unterstützung für Dekoratoren in seiner Keynote-Präsentation auf dem DevDay bei der 10. Python-Konferenz, obwohl er später sagte, es sei nur eine von mehreren Erweiterungen gewesen, die er dort „halbernst“ vorgeschlagen habe. Michael Hudson brachte das Thema kurz nach der Konferenz auf python-dev zur Sprache und schrieb die anfängliche Klammersyntax einer früheren Einreichung auf comp.lang.python von Gareth McCaughan zu.

Klassendekorationen scheinen ein offensichtlicher nächster Schritt zu sein, da Klassendefinitionen und Funktionsdefinitionen syntaktisch ähnlich sind, aber Guido bleibt unentschlossen, und Klassendekoratoren werden fast sicher nicht in Python 2.4 enthalten sein.

Die Diskussion setzte sich von Februar 2002 bis Juli 2004 auf und ab auf python-dev fort. Hunderte von Beiträgen wurden gemacht, wobei Leute viele mögliche Syntaxvariationen vorschlugen. Guido nahm eine Liste von Vorschlägen zur EuroPython 2004 mit, wo eine Diskussion stattfand. Danach entschied er, dass wir die Java-ähnliche @-Dekorator-Syntax erhalten würden, und diese erschien erstmals in 2.4a2. Barry Warsaw nannte diese die ‚Pie-Decorator‘-Syntax, zu Ehren des Pie-thon Parrot Shootouts, der etwa zur gleichen Zeit wie die Dekorator-Syntax stattfand, und weil das @ ein wenig wie eine Torte aussieht. Guido legte seinen Fall dar auf Python-dev, einschließlich dieses Stücks über einige der (vielen) abgelehnten Formen.

Zum Namen „Decorator“

Es gab eine Reihe von Beschwerden über die Wahl des Namens ‚decorator‘ für diese Funktion. Die wichtigste ist, dass der Name nicht mit seiner Verwendung im GoF-Buch übereinstimmt. Der Name ‚decorator‘ ist wahrscheinlich mehr seiner Verwendung im Compilerbereich geschuldet – ein Syntaxbaum wird durchlaufen und annotiert. Es ist durchaus möglich, dass ein besserer Name auftaucht.

Designziele

Die neue Syntax sollte

  • für beliebige Wrapper funktionieren, einschließlich benutzerdefinierter aufrufbarer Objekte und der vorhandenen Builtins classmethod() und staticmethod(). Diese Anforderung bedeutet auch, dass eine Dekorator-Syntax die Übergabe von Argumenten an den Wrapper-Konstruktor unterstützen muss
  • mit mehreren Wrappern pro Definition funktionieren
  • offensichtlich machen, was passiert; zumindest sollte es offensichtlich sein, dass neue Benutzer es beim Schreiben ihres eigenen Codes sicher ignorieren können
  • eine Syntax sein, die „... leicht zu merken ist, sobald sie erklärt wurde“
  • zukünftige Erweiterungen nicht erschweren
  • einfach zu tippen sein; Programme, die sie verwenden, werden sie voraussichtlich sehr häufig verwenden
  • nicht erschweren, Code schnell zu scannen. Es sollte immer noch einfach sein, nach allen Definitionen, einer bestimmten Definition oder den Argumenten, die eine Funktion akzeptiert, zu suchen
  • sekundäre Support-Tools wie sprachsensitive Editoren und andere „Spielzeugparser-Tools da draußen“ nicht unnötig verkomplizieren
  • zukünftigen Compilern die Optimierung von Dekoratoren ermöglichen. Mit der Hoffnung, dass irgendwann ein JIT-Compiler für Python existieren wird, erfordert dies, dass die Syntax für Dekoratoren vor der Funktionsdefinition kommt
  • von Ende der Funktion, wo sie derzeit versteckt ist, nach vorne verschoben werden, wo sie mehr ins Auge fällt ins Auge fällt

Andrew Kuchling hat Links zu vielen der Diskussionen über Motivationen und Anwendungsfälle in seinem Blog. Besonders hervorzuheben ist Jim Huginins Liste von Anwendungsfällen.

Aktuelle Syntax

Die aktuelle Syntax für Funktionsdekreatoren, wie in Python 2.4a2 implementiert, ist

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

Dies ist äquivalent zu

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

ohne die zwischenzeitliche Zuweisung an die Variable func. Die Dekoratoren befinden sich in der Nähe der Funktionsdeklaration. Das @-Zeichen macht deutlich, dass hier etwas Neues geschieht.

Die Begründung für die Reihenfolge der Anwendung (von unten nach oben) ist, dass sie der üblichen Reihenfolge der Funktionsanwendung entspricht. In der Mathematik übersetzt sich die Komposition von Funktionen (g o f)(x) zu g(f(x)). In Python übersetzt sich @g @f def foo() zu foo=g(f(foo).

Die Dekoratoranweisung ist in dem, was sie akzeptieren kann, begrenzt – beliebige Ausdrücke funktionieren nicht. Guido bevorzugte dies wegen eines Bauchgefühls.

Die aktuelle Syntax erlaubt auch Dekorator-Deklarationen, die eine Funktion aufrufen, die einen Dekorator zurückgibt

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
    pass

Dies ist äquivalent zu

func = decomaker(argA, argB, ...)(func)

Die Begründung für eine Funktion, die einen Dekorator zurückgibt, ist, dass der Teil nach dem @-Zeichen als Ausdruck betrachtet werden kann (obwohl syntaktisch auf nur eine Funktion beschränkt), und was auch immer dieser Ausdruck zurückgibt, wird aufgerufen. Siehe Deklarationsargumente.

Syntax-Alternativen

Es gab eine große Anzahl verschiedener Syntaxen, die vorgeschlagen wurden – anstatt zu versuchen, diese einzelnen Syntaxen durchzugehen, ist es lohnenswert, die Syntaxdiskussion in eine Reihe von Bereichen zu zerlegen. Der Versuch, jede mögliche Syntax einzeln zu diskutieren, wäre Wahnsinn und würde ein völlig unhandliches PEP ergeben.

Decorator-Position

Der erste Syntaxpunkt ist die Platzierung der Dekoratoren. Für die folgenden Beispiele verwenden wir die @-Syntax, die in 2.4a2 verwendet wird.

Dekoratoren vor der def-Anweisung sind die erste Alternative, und die Syntax, die in 2.4a2 verwendet wird

@classmethod
def foo(arg1,arg2):
    pass

@accepts(int,int)
@returns(float)
def bar(low,high):
    pass

Es gab eine Reihe von Einwänden gegen diese Platzierung – der wichtigste ist, dass es der erste echte Fall in Python ist, in dem eine Codezeile eine nachfolgende Zeile beeinflusst. Die in 2.4a3 verfügbare Syntax erfordert einen Dekorator pro Zeile (in a2 konnten mehrere Dekoratoren in derselben Zeile angegeben werden), und die endgültige Entscheidung für 2.4 final blieb bei einem Dekorator pro Zeile.

Leute beschwerten sich auch, dass die Syntax bei mehreren Dekoratoren schnell unhandlich wurde. Es wurde jedoch darauf hingewiesen, dass die Wahrscheinlichkeit, dass eine große Anzahl von Dekoratoren auf eine einzelne Funktion angewendet wird, gering ist und dies daher keine große Sorge darstellt.

Einige der Vorteile dieser Form sind, dass die Dekoratoren außerhalb des Methodenkörpers leben – sie werden offensichtlich zur Zeit der Funktionsdefinition ausgeführt.

Ein weiterer Vorteil ist, dass ein Präfix zur Funktionsdefinition der Idee entspricht, über eine Änderung der Semantik des Codes informiert zu werden, bevor der Code selbst vorhanden ist, sodass man weiß, wie die Semantik des Codes richtig zu interpretieren ist, ohne zurückgehen und seine anfänglichen Wahrnehmungen ändern zu müssen, wenn die Syntax nicht vor der Funktionsdefinition kam.

Guido entschied er bevorzugte es, die Dekoratoren auf der Zeile vor dem ‚def‘ zu haben, da man das Gefühl hatte, dass eine lange Argumentliste bedeuten würde, dass die Dekoratoren „versteckt“ würden

Die zweite Form sind die Dekoratoren zwischen dem def und dem Funktionsnamen oder dem Funktionsnamen und der Argumentliste

def @classmethod foo(arg1,arg2):
    pass

def @accepts(int,int),@returns(float) bar(low,high):
    pass

def foo @classmethod (arg1,arg2):
    pass

def bar @accepts(int,int),@returns(float) (low,high):
    pass

Es gibt ein paar Einwände gegen diese Form. Der erste ist, dass sie die ‚Greppabilität‘ der Quelle leicht bricht – man kann nicht mehr nach ‚def foo(‘ suchen und die Definition der Funktion finden. Der zweite, ernstere Einwand ist, dass im Falle mehrerer Dekoratoren die Syntax extrem unhandlich wäre.

Die nächste Form, die eine Reihe starker Befürworter hatte, ist es, die Dekoratoren zwischen der Argumentliste und dem abschließenden : in der ‚def‘-Zeile zu haben

def foo(arg1,arg2) @classmethod:
    pass

def bar(low,high) @accepts(int,int),@returns(float):
    pass

Guido fasste die Argumente gegen diese Form (von denen viele auch für die vorherige Form gelten) zusammen als

  • sie versteckt entscheidende Informationen (z. B. dass es sich um eine statische Methode handelt) nach der Signatur, wo sie leicht übersehen werden
  • es ist leicht, den Übergang von einer langen Argumentliste zu einer langen Dekoratorliste zu übersehen
  • es ist umständlich, eine Dekoratorliste zum Wiederverwenden zu kopieren und einzufügen, da sie mitten auf einer Zeile beginnt und endet

Die nächste Form ist, dass die Dekoratorsyntax innerhalb des Methodenkörpers am Anfang steht, an derselben Stelle, an der Docstrings derzeit leben

def foo(arg1,arg2):
    @classmethod
    pass

def bar(low,high):
    @accepts(int,int)
    @returns(float)
    pass

Der Haupteinwand gegen diese Form ist, dass sie ein „Hineinschauen“ in den Methodenkörper erfordert, um die Dekoratoren zu bestimmen. Darüber hinaus wird der Code, obwohl er sich im Methodenkörper befindet, nicht ausgeführt, wenn die Methode ausgeführt wird. Guido war der Meinung, dass Docstrings kein gutes Gegenbeispiel seien und dass es durchaus möglich sei, dass ein ‚Docstring‘-Dekorator dabei helfen könnte, den Docstring aus dem Funktionskörper herauszubewegen.

Die letzte Form ist ein neuer Block, der den Code der Methode umschließt. Für dieses Beispiel verwenden wir ein ‚decorate‘-Schlüsselwort, da es mit der @-Syntax keinen Sinn ergibt.

decorate:
    classmethod
    def foo(arg1,arg2):
        pass

decorate:
    accepts(int,int)
    returns(float)
    def bar(low,high):
        pass

Diese Form würde zu inkonsistenten Einrückungen für dekorierte und undekorierte Methoden führen. Darüber hinaus würde der Körper einer dekorierten Methode drei Einrückungsebenen tief beginnen.

Syntaxformen

  • @decorator:
    @classmethod
    def foo(arg1,arg2):
        pass
    
    @accepts(int,int)
    @returns(float)
    def bar(low,high):
        pass
    

    Die Hauptkritikpunkte gegen diese Syntax sind, dass das @-Symbol derzeit nicht in Python verwendet wird (und sowohl in IPython als auch in Leo verwendet wird) und dass das @-Symbol nicht aussagekräftig ist. Ein weiterer Kritikpunkt ist, dass dieses derzeit ungenutzte Zeichen (aus einem begrenzten Satz) für etwas „verschwendet“ wird, das nicht als Hauptanwendung wahrgenommen wird.

  • |decorator:
    |classmethod
    def foo(arg1,arg2):
        pass
    
    |accepts(int,int)
    |returns(float)
    def bar(low,high):
        pass
    

    Dies ist eine Variante der @decorator-Syntax – sie hat den Vorteil, dass sie IPython und Leo nicht stört. Ihr Hauptnachteil im Vergleich zur @-Syntax ist, dass das |-Symbol sowohl wie ein großes I als auch wie ein kleines l aussieht.

  • Listen-Syntax
    [classmethod]
    def foo(arg1,arg2):
        pass
    
    [accepts(int,int), returns(float)]
    def bar(low,high):
        pass
    

    Der Hauptkritikpunkt an der Listen-Syntax ist, dass sie derzeit eine Bedeutung hat (wenn sie in der Form vor der Methode verwendet wird). Es fehlt auch jeder Hinweis darauf, dass der Ausdruck ein Dekorator ist.

  • Listen-Syntax mit anderen Klammern (<...>, [[...]], …)
    <classmethod>
    def foo(arg1,arg2):
        pass
    
    <accepts(int,int), returns(float)>
    def bar(low,high):
        pass
    

    Keine dieser Alternativen fand viel Anklang. Die Alternativen, die eckige Klammern beinhalten, machen nur offensichtlich, dass das Dekorator-Konstrukt keine Liste ist. Sie erleichtern das Parsen in keiner Weise. Die ‚<…>‘-Alternative birgt Parsing-Probleme, da ‚<‘ und ‚>‘ bereits als ungepaarte Zeichen interpretiert werden. Sie birgt eine weitere Parsing-Mehrdeutigkeit, da ein Größer-als-Zeichen ein größeres als-Symbol anstelle eines schließenden Delektators sein könnte.

  • decorate()

    Der Vorschlag decorate() war, keine neue Syntax zu implementieren – stattdessen eine magische Funktion, die Introspektion verwendet, um die folgende Funktion zu manipulieren. Sowohl Jp Calderone als auch Philip Eby produzierten Implementierungen von Funktionen, die dies taten. Guido war ziemlich fest dagegen – ohne neue Syntax ist die Magie einer solchen Funktion extrem hoch

    Die Verwendung von Funktionen mit „Aktion auf Distanz“ über sys.settraceback mag für eine obskure Funktion in Ordnung sein, die sonst nicht zu haben ist, aber keine Sprachänderungen verdient; aber das ist nicht die Situation für Dekoratoren. Die weit verbreitete Ansicht ist hier, dass Dekoratoren als syntaktisches Merkmal hinzugefügt werden müssen, um die Probleme mit der Postfix-Notation von 2.2 und 2.3 zu vermeiden. Dekoratoren sind als wichtiges neues Sprachmerkmal vorgesehen und ihr Design muss zukunftsorientiert sein, nicht durch das eingeschränkt, was in 2.3 implementiert werden kann.
  • neues Schlüsselwort (und Block)

    Diese Idee war die Konsensalternative aus comp.lang.python (mehr dazu unter Community Consensus unten.) Robert Brewer schrieb ein detailliertes J2-Vorschlagsdokument, das die Argumente für diese Form darlegt. Die anfänglichen Probleme mit dieser Form sind

    • Sie erfordert ein neues Schlüsselwort und daher eine from __future__ import decorators-Anweisung.
    • Die Wahl des Schlüsselworts ist umstritten. Jedoch using setzte sich als Konsenswahl durch und wird im Vorschlag und der Implementierung verwendet.
    • Die Schlüsselwort/Block-Form erzeugt etwas, das wie ein normaler Codeblock aussieht, aber keiner ist. Versuche, Anweisungen in diesem Block zu verwenden, führen zu einem Syntaxfehler, was Benutzer verwirren kann.

    Ein paar Tage später lehnte Guido den Vorschlag ab aus zwei Hauptgründen, erstens

    … die syntaktische Form eines eingerückten Blocks suggeriert stark, dass sein Inhalt eine Sequenz von Anweisungen sein sollte, aber tatsächlich ist es das nicht – nur Ausdrücke sind erlaubt, und es gibt eine implizite „Sammlung“ dieser Ausdrücke, bis sie auf die nachfolgende Funktionsdefinition angewendet werden können. …

    und zweitens

    … das Schlüsselwort, das die Zeile beginnt, die einen Block einleitet, zieht viel Aufmerksamkeit auf sich. Dies gilt für „if“, „while“, „for“, „try“, „def“ und „class“. Aber das Schlüsselwort „using“ (oder jedes andere Schlüsselwort an seiner Stelle) verdient diese Aufmerksamkeit nicht; der Schwerpunkt sollte auf dem Dekorator oder den Dekoratoren innerhalb der Suite liegen, da dies die wichtigen Modifikatoren der folgenden Funktionsdefinition sind. …

    Die Leser werden eingeladen, die vollständige Antwort zu lesen.

  • Andere Formen

    Es gibt viele andere Varianten und Vorschläge auf der Wiki-Seite.

Warum @?

Es gibt eine gewisse Geschichte in Java, die @ zunächst als Marker in Javadoc-Kommentaren und später in Java 1.5 für Annotationen verwendete, die Python-Dekoratoren ähneln. Die Tatsache, dass @ zuvor nicht als Token in Python verwendet wurde, bedeutet auch, dass klar ist, dass kein solcher Code von einer früheren Version von Python geparst werden kann, was möglicherweise zu subtilen semantischen Fehlern führt. Es bedeutet auch, dass die Mehrdeutigkeit, was ein Dekorator ist und was nicht, beseitigt ist. Dennoch ist @ immer noch eine ziemlich willkürliche Wahl. Einige haben vorgeschlagen, stattdessen | zu verwenden.

Für Syntaxoptionen, die eine Listen-ähnliche Syntax verwenden (unabhängig davon, wo sie erscheint), um die Dekoratoren anzugeben, wurden einige Alternativen vorgeschlagen: [|...|], *[...]* und <...>.

Aktuelle Implementierung, Geschichte

Guido bat um einen Freiwilligen, um seine bevorzugte Syntax zu implementieren, und Mark Russell trat hervor und postete einen Patch an SF. Diese neue Syntax war in 2.4a2 verfügbar.

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

Dies ist äquivalent zu

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

jedoch ohne die Zwischenerstellung einer Variablen namens func.

Die in 2.4a2 implementierte Version erlaubte mehrere @decorator-Klauseln auf einer einzigen Zeile. In 2.4a3 wurde dies verschärft, sodass nur noch ein Dekorator pro Zeile erlaubt ist.

Ein früherer Patch von Michael Hudson, der die List-after-def-Syntax implementiert, ist ebenfalls noch im Umlauf.

Nach der Veröffentlichung von 2.4a2 erklärte Guido als Reaktion auf das Community-Feedback, dass er einen Community-Vorschlag erneut prüfen werde, wenn die Community einen Community-Konsens, einen anständigen Vorschlag und eine Implementierung vorlegen könne. Nach einer erstaunlichen Anzahl von Beiträgen, die eine riesige Menge an Alternativen im Python-Wiki sammelten, entstand ein Community-Konsens (unten). Guido lehnte diese alternative Form anschließend ab, fügte aber hinzu

In Python 2.4a3 (das diese Woche am Donnerstag veröffentlicht wird) bleibt alles wie derzeit in CVS. Für 2.4b1 werde ich eine Änderung von @ zu einem anderen Einzelzeichen in Betracht ziehen, obwohl ich denke, dass @ den Vorteil hat, das gleiche Zeichen wie ein ähnliches Feature in Java zu sein. Es wurde argumentiert, dass es nicht ganz dasselbe ist, da @ in Java für Attribute verwendet wird, die die Semantik nicht ändern. Aber Pythons dynamische Natur macht, dass seine syntaktischen Elemente nie ganz dieselbe Bedeutung haben wie ähnliche Konstrukte in anderen Sprachen, und es gibt definitiv eine signifikante Überlappung. Bezüglich der Auswirkungen auf Drittanbieter-Tools: Der Autor von IPython glaubt nicht, dass es große Auswirkungen haben wird; der Autor von Leo hat gesagt, dass Leo überleben wird (obwohl es ihm und seinen Benutzern einige Übergangsschwierigkeiten bereiten wird). Ich erwarte tatsächlich, dass die Wahl eines Zeichens, das bereits anderswo in Pythons Syntax verwendet wird, für externe Tools schwieriger anzupassen sein könnte, da das Parsen in diesem Fall subtiler sein muss. Aber ich bin ehrlich gesagt unentschlossen, also gibt es hier etwas Spielraum. Ich möchte zu diesem Zeitpunkt keine weiteren syntaktischen Alternativen mehr in Betracht ziehen: die Bürokratie muss irgendwann aufhören, jeder hat gesagt, was er zu sagen hat, und die Show muss weitergehen.

Gemeinschaftskonsens

Dieser Abschnitt dokumentiert die abgelehnten J2-Syntaxen und wird zur historischen Vollständigkeit aufgenommen.

Der auf comp.lang.python entstandene Konsens war die vorgeschlagene J2-Syntax (der „J2“ war, wie sie auf der PythonDecorators-Wiki-Seite referenziert wurde): das neue Schlüsselwort using, das einen Block von Dekoratoren vor der def-Anweisung voranstellt. Zum Beispiel

using:
    classmethod
    synchronized(lock)
def func(cls):
    pass

Die Hauptargumente für diese Syntax fallen unter die Doktrin „Lesbarkeit zählt“. Kurz gesagt, sie sind

  • Ein Block ist besser als mehrere @-Zeilen. Das using-Schlüsselwort und der Block verwandeln die Einzelblock-Anweisung def in ein Mehrblock-Verbundkonstrukt, ähnlich wie try/finally und andere.
  • Ein Schlüsselwort ist besser als Satzzeichen für ein neues Token. Ein Schlüsselwort entspricht der bestehenden Verwendung von Tokens. Keine neue Token-Kategorie ist notwendig. Ein Schlüsselwort unterscheidet Python-Dekoratoren von Java-Annotationen und .Net-Attributen, die signifikant unterschiedliche Dinge sind.

Robert Brewer schrieb einen detaillierten Vorschlag für diese Form, und Michael Sparks produzierte einen Patch.

Wie bereits erwähnt, lehnte Guido diese Form ab und legte seine Probleme damit in einer Nachricht an python-dev und comp.lang.python dar.

Beispiele

Ein Großteil der Diskussionen auf comp.lang.python und der python-dev Mailingliste konzentriert sich auf die Verwendung von Dekoratoren als saubereren Weg zur Verwendung der Builtins staticmethod() und classmethod(). Diese Funktionalität ist weitaus leistungsfähiger. Dieser Abschnitt präsentiert einige Anwendungsbeispiele.

  1. Definieren einer Funktion, die beim Beenden ausgeführt werden soll. Beachten Sie, dass die Funktion im üblichen Sinne nicht tatsächlich „umhüllt“ wird.
    def onexit(f):
        import atexit
        atexit.register(f)
        return f
    
    @onexit
    def func():
        ...
    

    Beachten Sie, dass dieses Beispiel wahrscheinlich nicht für den realen Gebrauch geeignet ist, sondern nur zu Demonstrationszwecken dient.

  2. Definieren einer Klasse mit einer Singleton-Instanz. Beachten Sie, dass findige Programmierer nach dem Verschwinden der Klasse kreativer sein müssten, um weitere Instanzen zu erstellen. (Von Shane Hathaway auf python-dev.)
    def singleton(cls):
        instances = {}
        def getinstance():
            if cls not in instances:
                instances[cls] = cls()
            return instances[cls]
        return getinstance
    
    @singleton
    class MyClass:
        ...
    
  3. Hinzufügen von Attributen zu einer Funktion. (Basierend auf einem Beispiel, das Anders Munch auf python-dev gepostet hat.)
    def attrs(**kwds):
        def decorate(f):
            for k in kwds:
                setattr(f, k, kwds[k])
            return f
        return decorate
    
    @attrs(versionadded="2.2",
           author="Guido van Rossum")
    def mymethod(f):
        ...
    
  4. Erzwingen von Funktionsargument- und Rückgabetypen. Beachten Sie, dass dies das func_name-Attribut von der alten zur neuen Funktion kopiert. func_name war in Python 2.4a3 schreibbar
    def accepts(*types):
        def check_accepts(f):
            assert len(types) == f.func_code.co_argcount
            def new_f(*args, **kwds):
                for (a, t) in zip(args, types):
                    assert isinstance(a, t), \
                           "arg %r does not match %s" % (a,t)
                return f(*args, **kwds)
            new_f.func_name = f.func_name
            return new_f
        return check_accepts
    
    def returns(rtype):
        def check_returns(f):
            def new_f(*args, **kwds):
                result = f(*args, **kwds)
                assert isinstance(result, rtype), \
                       "return value %r does not match %s" % (result,rtype)
                return result
            new_f.func_name = f.func_name
            return new_f
        return check_returns
    
    @accepts(int, (int,float))
    @returns((int,float))
    def func(arg1, arg2):
        return arg1 * arg2
    
  5. Erklären, dass eine Klasse eine bestimmte (Menge von) Schnittstelle(n) implementiert. Dies stammt aus einem Beitrag von Bob Ippolito auf python-dev basierend auf Erfahrungen mit PyProtocols.
    def provides(*interfaces):
         """
         An actual, working, implementation of provides for
         the current implementation of PyProtocols.  Not
         particularly important for the PEP text.
         """
         def provides(typ):
             declareImplementation(typ, instancesProvide=interfaces)
             return typ
         return provides
    
    class IBar(Interface):
         """Declare something about IBar here"""
    
    @provides(IBar)
    class Foo(object):
            """Implement something here..."""
    

Natürlich sind all diese Beispiele heute möglich, wenn auch ohne syntaktische Unterstützung.

(Nicht mehr) offene Fragen

  1. Es ist noch nicht sicher, ob Klassendekoratoren zu einem späteren Zeitpunkt in die Sprache aufgenommen werden. Guido äußerte Skepsis gegenüber dem Konzept, aber verschiedene Leute haben einige starke Argumente (suchen Sie nach PEP 318 -- posting draft) in python-dev dafür vorgebracht. Es ist äußerst unwahrscheinlich, dass Klassendekoratoren in Python 2.4 enthalten sein werden.

    PEP 3129 schlägt die Hinzufügung von Klassendekoratoren ab Python 2.6 vor.

  2. Die Wahl des @-Zeichens wird vor Python 2.4b1 erneut geprüft.

    Am Ende wurde das @-Zeichen beibehalten.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0318.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT