Ich möchte einen Python-Dekorator erstellen, der entweder mit Parametern verwendet werden kann:
@redirect_output("somewhere.log")
def foo():
....
oder ohne sie (zum Beispiel um die Ausgabe standardmäßig auf stderr umzuleiten):
@redirect_output
def foo():
....
Ist das überhaupt möglich?
Beachten Sie, dass ich nicht nach einer anderen Lösung für das Problem der Umleitung von Ausgaben suche, sondern nur ein Beispiel für die Syntax, die ich erreichen möchte.
@redirect_output
ist bemerkenswert wenig aussagekräftig. Ich würde vorschlagen, dass es eine schlechte Idee ist. Verwenden Sie die erste Form und vereinfachen Sie Ihr Leben sehr.Antworten:
Ich weiß, dass diese Frage alt ist, aber einige der Kommentare sind neu, und obwohl alle praktikablen Lösungen im Wesentlichen gleich sind, sind die meisten von ihnen nicht sehr sauber oder leicht zu lesen.
Wie die Antwort von thobe sagt, besteht die einzige Möglichkeit, beide Fälle zu behandeln, darin, nach beiden Szenarien zu suchen. Der einfachste Weg ist einfach zu überprüfen, ob es ein einzelnes Argument gibt und es callabe ist (HINWEIS: Zusätzliche Überprüfungen sind erforderlich, wenn Ihr Dekorateur nur 1 Argument akzeptiert und es sich zufällig um ein aufrufbares Objekt handelt):
Im ersten Fall tun Sie das, was ein normaler Dekorateur tut, und geben eine modifizierte oder umschlossene Version der übergebenen Funktion zurück.
Im zweiten Fall geben Sie einen 'neuen' Dekorateur zurück, der die mit * args, ** kwargs übergebenen Informationen irgendwie verwendet.
Das ist in Ordnung und alles, aber es für jeden Dekorateur, den Sie machen, aufschreiben zu müssen, kann ziemlich nervig und nicht so sauber sein. Stattdessen wäre es schön, unsere Dekorateure automatisch modifizieren zu können, ohne sie neu schreiben zu müssen ... aber dafür sind Dekorateure da!
Mit dem folgenden Dekorateur können wir unsere Dekorateure so dekrieren, dass sie mit oder ohne Argumente verwendet werden können:
Jetzt können wir unsere Dekorateure mit @doublewrap dekorieren und sie werden mit und ohne Argumente mit einer Einschränkung arbeiten:
Ich habe oben erwähnt, sollte aber hier wiederholen, dass die Prüfung in diesem Dekorateur eine Annahme über die Argumente macht, die ein Dekorateur erhalten kann (nämlich dass er kein einziges, aufrufbares Argument empfangen kann). Da wir es jetzt auf jeden Generator anwendbar machen, muss es berücksichtigt oder modifiziert werden, wenn es widersprochen wird.
Das Folgende demonstriert seine Verwendung:
quelle
Die Verwendung von Schlüsselwortargumenten mit Standardwerten (wie von kquinn vorgeschlagen) ist eine gute Idee, erfordert jedoch die Angabe der Klammer:
Wenn Sie eine Version wünschen, die ohne die Klammer auf dem Dekorator funktioniert, müssen Sie beide Szenarien in Ihrem Dekorationscode berücksichtigen.
Wenn Sie Python 3.0 verwenden, können Sie hierfür nur Argumente mit Schlüsselwörtern verwenden:
In Python 2.x kann dies mit varargs Tricks emuliert werden:
Mit jeder dieser Versionen können Sie Code wie folgt schreiben:
quelle
your code here
? Wie nennt man die Funktion, die dekoriert ist?fn(*args, **kwargs)
funktioniert nichtdef f(a = 5): return MyDecorator( a = a)
und esclass MyDecorator( object ): def __init__( self, a = 5 ): ....
tut mir leid, dass es schwer ist, es in einen Kommentar zu schreiben, aber ich hoffe, das ist einfach genug zu verstehenIch weiß, dass dies eine alte Frage ist, aber ich mag keine der vorgeschlagenen Techniken, deshalb wollte ich eine andere Methode hinzufügen. Ich habe gesehen, dass Django eine wirklich saubere Methode in ihrem
login_required
Dekorateur verwendetdjango.contrib.auth.decorators
. Wie Sie in den Dokumenten des Dekorateurs sehen können , kann es allein als@login_required
oder mit Argumenten verwendet werden@login_required(redirect_field_name='my_redirect_field')
.Die Art und Weise, wie sie es tun, ist ganz einfach. Sie fügen vor den Dekorationsargumenten ein
kwarg
(function=None
) hinzu. Wenn der Dekorator alleine verwendet wird,function
ist dies die eigentliche Funktion, die er dekoriert, während dies der Fall ist, wenn er mit Argumenten aufgerufenfunction
wirdNone
.Beispiel:
Ich finde diesen Ansatz, den Django verwendet, eleganter und leichter zu verstehen als alle anderen hier vorgeschlagenen Techniken.
quelle
function
und dann die Dinge zu brechen , weil die Dekorateur versucht so zu nennen ersten Positions arg , als ob es Ihr dekoriert Funktion waren.Sie müssen beide Fälle erkennen, z. B. anhand des Typs des ersten Arguments, und dementsprechend entweder den Wrapper (bei Verwendung ohne Parameter) oder einen Dekorator (bei Verwendung mit Argumenten) zurückgeben.
Wenn die
@redirect_output("output.log")
Syntax verwendet wird,redirect_output
wird sie mit einem einzelnen Argument aufgerufen"output.log"
und muss einen Dekorateur zurückgeben, der die zu dekorierende Funktion als Argument akzeptiert. Bei Verwendung als@redirect_output
wird es direkt mit der Funktion aufgerufen, die als Argument dekoriert werden soll.Oder mit anderen Worten: die
@
Syntax muss ein Ausdruck folgen, dessen Ergebnis eine Funktion ist, die eine zu dekorierende Funktion als einziges Argument akzeptiert und die dekorierte Funktion zurückgibt. Der Ausdruck selbst kann ein Funktionsaufruf sein, was bei der Fall ist@redirect_output("output.log")
. Gewunden, aber wahr :-)quelle
Einige Antworten hier sprechen Ihr Problem bereits gut an. In Bezug auf den Stil ziehe ich es jedoch vor, diese Dekorationsproblematik mithilfe von zu lösen
functools.partial
, wie in David Beazleys Python Cookbook 3 vorgeschlagen :Während ja, können Sie einfach tun
Ohne funky Workarounds finde ich es seltsam und ich mag die Möglichkeit, einfach mit zu dekorieren
@decorator
.Was das sekundäre Missionsziel betrifft, wird in diesem Beitrag zum Stapelüberlauf die Umleitung der Ausgabe einer Funktion behandelt .
Wenn Sie tiefer eintauchen möchten, lesen Sie Kapitel 9 (Metaprogrammierung) in Python Cookbook 3 , das frei verfügbar ist und online gelesen werden kann .
Ein Teil dieses Materials wird in Beazleys großartigem YouTube-Video Python 3 Metaprogramming live (und noch mehr!) Vorgeführt .
Viel Spaß beim Codieren :)
quelle
Ein Python-Dekorateur wird grundlegend anders aufgerufen, je nachdem, ob Sie ihm Argumente geben oder nicht. Die Dekoration ist eigentlich nur ein (syntaktisch eingeschränkter) Ausdruck.
In Ihrem ersten Beispiel:
Die Funktion
redirect_output
wird mit dem angegebenen Argument aufgerufen, von dem erwartet wird, dass es eine Dekorationsfunktion zurückgibt, die selbstfoo
als Argument aufgerufen wird , und von der (schließlich!) erwartet wird, dass sie die endgültige dekorierte Funktion zurückgibt.Der entsprechende Code sieht folgendermaßen aus:
Der entsprechende Code für Ihr zweites Beispiel sieht folgendermaßen aus:
So Sie können tun , was Sie aber nicht in einem völlig nahtlos mögen:
Dies sollte in Ordnung sein, es sei denn, Sie möchten eine Funktion als Argument für Ihren Dekorateur verwenden. In diesem Fall geht der Dekorateur fälschlicherweise davon aus, dass er keine Argumente enthält. Es schlägt auch fehl, wenn diese Dekoration auf eine andere Dekoration angewendet wird, die keinen Funktionstyp zurückgibt.
Eine alternative Methode besteht lediglich darin, dass die Dekorationsfunktion immer aufgerufen wird, auch wenn sie keine Argumente enthält. In diesem Fall würde Ihr zweites Beispiel folgendermaßen aussehen:
Der Dekorationsfunktionscode würde folgendermaßen aussehen:
quelle
Tatsächlich kann der Vorbehaltsfall in der Lösung von @ bj0 leicht überprüft werden:
Hier sind einige Testfälle für diese ausfallsichere Version von
meta_wrap
.quelle
Um eine vollständigere Antwort als die oben genannten zu geben:
Nein, es gibt keinen generischen Weg, da derzeit in der Python-Sprache etwas fehlt, um die beiden unterschiedlichen Anwendungsfälle zu erkennen.
Jedoch Ja , wie bereits von anderen Antworten wie darauf hingewiesen ,
bj0
s , gibt es eine klobig Abhilfe , die die Art und Wert des ersten Positions Arguments zu überprüfen ist , empfängt (und zu überprüfen , ob keine andere Argumente Nicht-Standardwert). Wenn Sie garantiert sind, dass Benutzer nie sicher einen Aufruf als erstes Argument Ihres Dekorateurs übergeben, können Sie diese Problemumgehung verwenden. Beachten Sie, dass dies auch für Klassendekorateure gilt (ersetzen Sie callable durch class im vorherigen Satz).Um sicherzugehen, habe ich da draußen einiges recherchiert und sogar eine Bibliothek mit dem Namen implementiert
decopatch
, die eine Kombination aller oben genannten Strategien (und viele weitere, einschließlich Selbstbeobachtung) verwendet, um "was auch immer die intelligenteste Problemumgehung ist" durchzuführen auf Ihre Bedürfnisse.Aber ehrlich gesagt wäre es das Beste, hier keine Bibliothek zu benötigen und diese Funktion direkt aus der Python-Sprache zu erhalten. Wenn Sie wie ich der Meinung sind, dass es schade ist, dass die Python-Sprache bis heute keine ordentliche Antwort auf diese Frage liefern kann, zögern Sie nicht, diese Idee im Python-Bugtracker zu unterstützen : https: //bugs.python .org / issue36553 !
Vielen Dank für Ihre Hilfe, Python zu einer besseren Sprache zu machen :)
quelle
Dies macht den Job ohne viel Aufhebens:
quelle
Haben Sie Keyword-Argumente mit Standardwerten ausprobiert? Etwas wie
quelle
Im Allgemeinen können Sie in Python Standardargumente angeben ...
Ich bin mir nicht sicher, ob das auch mit Dekorateuren funktioniert. Ich kenne keinen Grund, warum es nicht so wäre.
quelle
Aufbauend auf der Antwort von vartec:
quelle
@redirect_output("somewhere.log") def foo()
Beispiel in der Frage als Dekorateur verwendet werden .