Angenommen, ich habe einen Dekorateur geschrieben, der etwas sehr Allgemeines tut. Beispielsweise können alle Argumente in einen bestimmten Typ konvertiert, eine Protokollierung durchgeführt, eine Memoisierung implementiert usw. werden.
Hier ist ein Beispiel:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
Alles gut soweit. Es gibt jedoch ein Problem. Die dekorierte Funktion behält nicht die Dokumentation der ursprünglichen Funktion bei:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Zum Glück gibt es eine Problemumgehung:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
Diesmal sind der Funktionsname und die Dokumentation korrekt:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Es gibt aber immer noch ein Problem: Die Funktionssignatur ist falsch. Die Information "* args, ** kwargs" ist so gut wie nutzlos.
Was ist zu tun? Ich kann mir zwei einfache, aber fehlerhafte Problemumgehungen vorstellen:
1 - Fügen Sie die richtige Signatur in die Dokumentzeichenfolge ein:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
Dies ist wegen der Duplizierung schlecht. Die Signatur wird in der automatisch generierten Dokumentation immer noch nicht richtig angezeigt. Es ist einfach, die Funktion zu aktualisieren und das Ändern der Dokumentzeichenfolge zu vergessen oder einen Tippfehler zu machen. [ Und ja, mir ist bewusst, dass der Docstring den Funktionskörper bereits dupliziert. Bitte ignorieren Sie dies; lustige_Funktion ist nur ein zufälliges Beispiel.]]
2 - Verwenden Sie für jede Unterschrift keinen Dekorateur oder einen Spezialdekorateur:
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
Dies funktioniert gut für eine Reihe von Funktionen mit identischer Signatur, ist jedoch im Allgemeinen nutzlos. Wie eingangs gesagt, möchte ich Dekorateure ganz generisch einsetzen können.
Ich suche nach einer Lösung, die vollständig allgemein und automatisch ist.
Die Frage ist also: Gibt es eine Möglichkeit, die dekorierte Funktionssignatur zu bearbeiten, nachdem sie erstellt wurde?
Kann ich andernfalls einen Dekorator schreiben, der die Funktionssignatur extrahiert und diese Informationen anstelle von "* kwargs, ** kwargs" verwendet, wenn die dekorierte Funktion erstellt wird? Wie extrahiere ich diese Informationen? Wie soll ich die dekorierte Funktion konstruieren - mit exec?
Irgendwelche anderen Ansätze?
inspect.Signature
zum Umgang mit dekorierten Funktionen beigetragen hat.Antworten:
Dekorationsmodul installieren :
Anpassung der Definition von
args_as_ints()
:Python 3.4+
functools.wraps()
von stdlib behält Signaturen seit Python 3.4 bei:functools.wraps()
ist mindestens seit Python 2.5 verfügbar, behält dort jedoch die Signatur nicht bei:Hinweis:
*args, **kwargs
anstelle vonx, y, z=3
.quelle
functools.wraps()
bereits Signaturen in Python 3.4+ auf (wie in der Antwort angegeben). Meinst du, die Einstellungwrapper.__signature__
hilft bei früheren Versionen? (help()
Zeigt die korrekte Signatur in Python 3.4 an. Warum denkst dufunctools.wraps()
ist kaputt und nicht IPython?help()
der Tatsache, dass das richtige Ergebnis erzielt wird, stellt sich die Frage, welche Software repariert werden sollte:functools.wraps()
oder IPython? In jedem Fall ist die manuelle Zuweisung__signature__
bestenfalls eine Problemumgehung - es ist keine langfristige Lösung.inspect.getfullargspec()
immer noch nicht die richtige Signatur fürfunctools.wraps
Python 3.4 zurückgegeben, die Sieinspect.signature()
stattdessen verwenden müssen.Dies wird mit der Standardbibliothek
functools
und speziell der Python-functools.wraps
Funktion gelöst, mit der " eine Wrapper-Funktion so aktualisiert werden soll, dass sie wie die Wrapped-Funktion aussieht ". Das Verhalten hängt jedoch von der Python-Version ab, wie unten gezeigt. Auf das Beispiel aus der Frage angewendet würde der Code folgendermaßen aussehen:Bei Ausführung in Python 3 würde dies Folgendes ergeben:
Der einzige Nachteil ist, dass in Python 2 die Argumentliste der Funktion nicht aktualisiert wird. Bei der Ausführung in Python 2 wird Folgendes erzeugt:
quelle
Es gibt ein Dekorationsmodul mit
decorator
Dekorator, das Sie verwenden können:Dann bleibt die Signatur und Hilfe der Methode erhalten:
EDIT: JF Sebastian hat darauf hingewiesen, dass ich die
args_as_ints
Funktion nicht geändert habe - sie ist jetzt behoben.quelle
Werfen Sie einen Blick auf die Dekorateur Modul - speziell die Dekorateur Dekorateur, die dieses Problem löst.
quelle
Zweite Option:
$ easy_install wrapt
Wrapt haben einen Bonus, Klassenunterschrift bewahren.
quelle
Wie oben in der Antwort von jfs kommentiert ; Wenn Sie sich mit der Signatur in Bezug auf das Erscheinungsbild (
help
, undinspect.signature
) befassen , ist die Verwendungfunctools.wraps
vollkommen in Ordnung.Wenn Sie sich mit der Signatur in Bezug auf das Verhalten befassen (insbesondere
TypeError
bei nicht übereinstimmenden Argumenten), wirdfunctools.wraps
diese nicht beibehalten. Sie sollten eherdecorator
dafür oder meine Verallgemeinerung der Core Engine namens verwendenmakefun
.Siehe auch diesen Beitrag über
functools.wraps
.quelle
inspect.getfullargspec
wird nicht durch Aufruf gespeichertfunctools.wraps
.