Wie kann ich zwei Dekoratoren in Python erstellen, die folgendes tun?
@makebold
@makeitalic
def say():
return "Hello"
...der zurückkehren sollte:
"<b><i>Hello</i></b>"
Wie kann ich zwei Dekoratoren in Python erstellen, die folgendes tun?
@makebold
@makeitalic
def say():
return "Hello"
...der zurückkehren sollte:
"<b><i>Hello</i></b>"
Python-Dekoratoren fügen einer anderen Funktion zusätzliche Funktionen hinzu
Ein Kursiv-Dekorator könnte wie folgt aussehen
def makeitalic(fn):
def newFunc():
return "<i>" + fn() + "</i>"
return newFunc
Beachten Sie, dass eine Funktion innerhalb einer Funktion definiert ist. Im Grunde wird eine Funktion durch die neu definierte Funktion ersetzt. Ein Beispiel: Ich habe diese Klasse
class foo:
def bar(self):
print "hi"
def foobar(self):
print "hi again"
Angenommen, ich möchte, dass beide Funktionen nach und vor ihrer Ausführung "---" ausgeben. Ich könnte ein print "---" vor und nach jeder print-Anweisung einfügen. Aber da ich mich nicht gerne wiederhole, werde ich einen Dekorator erstellen
def addDashes(fn): # notice it takes a function as an argument
def newFunction(self): # define a new function
print "---"
fn(self) # call the original function
print "---"
return newFunction
# Return the newly defined function - it will "replace" the original
Jetzt kann ich also meine Klasse ändern in
class foo:
@addDashes
def bar(self):
print "hi"
@addDashes
def foobar(self):
print "hi again"
Mehr über Dekorateure finden Sie unter http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
@Phoenix: Die self
ist erforderlich, weil das Argument newFunction()
definiert in addDashes()
wurde speziell entwickelt, um eine Methode Dekorator und nicht ein allgemeiner Funktionsdekorator. Der self
Argument stellt die Klasseninstanz dar und wird an Klassenmethoden weitergegeben, unabhängig davon, ob sie es verwenden oder nicht -- siehe den Abschnitt Dekorationsmethoden in der Antwort von @e-satis.
Usted könnte zwei separate Dekoratoren erstellen, die das tun, was Sie wollen, wie direkt unten dargestellt. Beachten Sie die Verwendung von *args, **kwargs
in der Erklärung des wrapped()
Funktion, die die dekorierte Funktion mit mehreren Argumenten unterstützt (was für das Beispiel nicht wirklich notwendig ist say()
Funktion, wird aber aus Gründen der Allgemeinheit aufgenommen).
Aus ähnlichen Gründen ist die functools.wraps
Dekorator wird verwendet, um die Meta-Attribute der umhüllten Funktion in die der dekorierten Funktion zu ändern. Dies macht Fehlermeldungen und eingebettete Funktionsdokumentation ( func.__doc__
) die der dekorierten Funktion anstelle von wrapped()
's.
from functools import wraps
def makebold(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapped
def makeitalic(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</i>"
return wrapped
@makebold
@makeitalic
def say():
return 'Hello'
print(say()) # -> <b><i>Hello</i></b>
Wie Sie sehen können, gibt es eine Menge doppelten Code in diesen beiden Dekoratoren. Angesichts dieser Ähnlichkeit wäre es besser für Sie, stattdessen einen generischen zu machen, der eigentlich ein Dekorationsmittelfabrik -mit anderen Worten, eine Dekoratorfunktion, die andere Dekoratoren erzeugt. Auf diese Weise gäbe es weniger Code-Wiederholungen und die DRY Grundsatz zu befolgen.
def html_deco(tag):
def decorator(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
return wrapped
return decorator
@html_deco('b')
@html_deco('i')
def greet(whom=''):
return 'Hello' + (' ' + whom) if whom else ''
print(greet('world')) # -> <b><i>Hello world</i></b>
Um den Code lesbarer zu machen, können Sie den werkseitig erzeugten Dekoratoren einen aussagekräftigeren Namen geben:
makebold = html_deco('b')
makeitalic = html_deco('i')
@makebold
@makeitalic
def greet(whom=''):
return 'Hello' + (' ' + whom) if whom else ''
print(greet('world')) # -> <b><i>Hello world</i></b>
oder sie sogar auf diese Weise kombinieren:
makebolditalic = lambda fn: makebold(makeitalic(fn))
@makebolditalic
def greet(whom=''):
return 'Hello' + (' ' + whom) if whom else ''
print(greet('world')) # -> <b><i>Hello world</i></b>
Während die obigen Beispiele alle funktionieren, beinhaltet der erzeugte Code eine Menge Overhead in Form von fremden Funktionsaufrufen, wenn mehrere Dekoratoren gleichzeitig angewendet werden. Je nach der genauen Verwendung (die z.B. I/O-gebunden sein könnte) ist dies vielleicht nicht von Bedeutung.
Wenn die Geschwindigkeit der dekorierten Funktion wichtig ist, kann der Overhead auf einen einzigen zusätzlichen Funktionsaufruf beschränkt werden, indem man eine etwas andere Dekorator-Factory-Funktion schreibt, die das Hinzufügen aller Tags auf einmal implementiert, so dass sie Code erzeugen kann, der die zusätzlichen Funktionsaufrufe vermeidet, die durch die Verwendung separater Dekoratoren für jedes Tag entstehen.
Dies erfordert mehr Code im Dekorator selbst, aber dieser läuft nur, wenn er auf Funktionsdefinitionen angewendet wird, nicht später, wenn sie selbst aufgerufen werden. Dies gilt auch für die Erstellung von besser lesbaren Namen durch die Verwendung von lambda
funktioniert wie zuvor beschrieben. Beispiel:
def multi_html_deco(*tags):
start_tags, end_tags = [], []
for tag in tags:
start_tags.append('<%s>' % tag)
end_tags.append('</%s>' % tag)
start_tags = ''.join(start_tags)
end_tags = ''.join(reversed(end_tags))
def decorator(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return start_tags + fn(*args, **kwargs) + end_tags
return wrapped
return decorator
makebolditalic = multi_html_deco('b', 'i')
@makebolditalic
def greet(whom=''):
return 'Hello' + (' ' + whom) if whom else ''
print(greet('world')) # -> <b><i>Hello world</i></b>
Eine andere Art, das Gleiche zu tun:
class bol(object):
def __init__(self, f):
self.f = f
def __call__(self):
return "<b>{}</b>".format(self.f())
class ita(object):
def __init__(self, f):
self.f = f
def __call__(self):
return "<i>{}</i>".format(self.f())
@bol
@ita
def sayhi():
return 'hi'
Oder noch flexibler:
class sty(object):
def __init__(self, tag):
self.tag = tag
def __call__(self, f):
def newf():
return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
return newf
@sty('b')
@sty('i')
def sayhi():
return 'hi'
Wie kann ich zwei Dekoratoren in Python erstellen, die folgendes tun?
Sie möchten, dass die folgende Funktion aufgerufen wird:
@makebold @makeitalic def say(): return "Hello"
Zur Rückkehr:
<b><i>Hello</i></b>
Um dies am einfachsten zu tun, machen Sie Dekoratoren, die Lambdas (anonyme Funktionen) zurückgeben, die über die Funktion (Closures) schließen und sie aufrufen:
def makeitalic(fn):
return lambda: '<i>' + fn() + '</i>'
def makebold(fn):
return lambda: '<b>' + fn() + '</b>'
Verwenden Sie sie nun nach Belieben:
@makebold
@makeitalic
def say():
return 'Hello'
und jetzt:
>>> say()
'<b><i>Hello</i></b>'
Aber wir scheinen die ursprüngliche Funktion fast verloren zu haben.
>>> say
<function <lambda> at 0x4ACFA070>
Um sie zu finden, müssten wir in die Schließung jedes Lambdas eindringen, von denen das eine im anderen verborgen ist:
>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>
Wenn wir also diese Funktion dokumentieren oder Funktionen ausschmücken wollen, die mehr als ein Argument annehmen, oder wenn wir einfach nur wissen wollen, welche Funktion wir in einer Debugging-Sitzung betrachten, müssen wir etwas mehr mit unserem Wrapper machen.
Wir haben den Dekorateur wraps
von der functools
Modul in der Standardbibliothek!
from functools import wraps
def makeitalic(fn):
# must assign/update attributes from wrapped function to wrapper
# __module__, __name__, __doc__, and __dict__ by default
@wraps(fn) # explicitly give function whose attributes it is applying
def wrapped(*args, **kwargs):
return '<i>' + fn(*args, **kwargs) + '</i>'
return wrapped
def makebold(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return '<b>' + fn(*args, **kwargs) + '</b>'
return wrapped
Es ist bedauerlich, dass es immer noch einige Standardformulierungen gibt, aber das ist so einfach, wie wir es machen können.
In Python 3 erhalten Sie außerdem __qualname__
y __annotations__
standardmäßig zugewiesen.
Also jetzt:
@makebold
@makeitalic
def say():
"""This function returns a bolded, italicized 'hello'"""
return 'Hello'
Und jetzt:
>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:
say(*args, **kwargs)
This function returns a bolded, italicized 'hello'
Wir sehen also, dass wraps
lässt die Wrapping-Funktion fast alles tun, außer uns genau zu sagen, was die Funktion als Argumente nimmt.
Es gibt andere Module, die versuchen, das Problem zu lösen, aber die Lösung ist noch nicht in der Standardbibliothek enthalten.
Ein Dekorator nimmt die Funktionsdefinition und erstellt eine neue Funktion, die diese Funktion ausführt und das Ergebnis umwandelt.
@deco
def do():
...
ist gleichbedeutend mit:
do = deco(do)
def deco(func):
def inner(letter):
return func(letter).upper() #upper
return inner
Diese
@deco
def do(number):
return chr(number) # number to letter
ist gleichbedeutend mit
def do2(number):
return chr(number)
do2 = deco(do2)
65 <=> 'a'
print(do(65))
print(do2(65))
>>> B
>>> B
Um den Dekorator zu verstehen, ist es wichtig zu wissen, dass der Dekorator eine neue Funktion do erstellt, die eine innere Funktion ist, die die Funktion ausführt und das Ergebnis umwandelt.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.