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>"
Diese Frage wurde schon lange beantwortet, aber ich dachte, ich würde meine Decorator-Klasse mit Ihnen teilen, die das Schreiben neuer Dekoratoren einfach und kompakt macht.
from abc import ABCMeta, abstractclassmethod
class Decorator(metaclass=ABCMeta):
""" Acts as a base class for all decorators """
def __init__(self):
self.method = None
def __call__(self, method):
self.method = method
return self.call
@abstractclassmethod
def call(self, *args, **kwargs):
return self.method(*args, **kwargs)
Zum einen denke ich, dass dies das Verhalten der Dekoratoren sehr klar macht, aber es macht es auch einfach, neue Dekoratoren sehr prägnant zu definieren. Für das oben aufgeführte Beispiel könnte man es dann wie folgt lösen:
class MakeBold(Decorator):
def call():
return "<b>" + self.method() + "</b>"
class MakeItalic(Decorator):
def call():
return "<i>" + self.method() + "</i>"
@MakeBold()
@MakeItalic()
def say():
return "Hello"
Sie können es auch für komplexere Aufgaben verwenden, wie zum Beispiel einen Dekorator, der die Funktion automatisch rekursiv auf alle Argumente in einem Iterator anwendet:
class ApplyRecursive(Decorator):
def __init__(self, *types):
super().__init__()
if not len(types):
types = (dict, list, tuple, set)
self._types = types
def call(self, arg):
if dict in self._types and isinstance(arg, dict):
return {key: self.call(value) for key, value in arg.items()}
if set in self._types and isinstance(arg, set):
return set(self.call(value) for value in arg)
if tuple in self._types and isinstance(arg, tuple):
return tuple(self.call(value) for value in arg)
if list in self._types and isinstance(arg, list):
return list(self.call(value) for value in arg)
return self.method(arg)
@ApplyRecursive(tuple, set, dict)
def double(arg):
return 2*arg
print(double(1))
print(double({'a': 1, 'b': 2}))
print(double({1, 2, 3}))
print(double((1, 2, 3, 4)))
print(double([1, 2, 3, 4, 5]))
Welche Drucke:
2
{'a': 2, 'b': 4}
{2, 4, 6}
(2, 4, 6, 8)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Beachten Sie, dass dieses Beispiel nicht die list
Typ in der Instanziierung des Dekorators, so dass in der abschließenden Druckanweisung die Methode auf die Liste selbst und nicht auf die Elemente der Liste angewendet wird.
#decorator.py
def makeHtmlTag(tag, *args, **kwds):
def real_decorator(fn):
css_class = " class='{0}'".format(kwds["css_class"]) \
if "css_class" in kwds else ""
def wrapped(*args, **kwds):
return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
return wrapped
# return decorator dont call it
return real_decorator
@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
return "hello world"
print hello()
Sie können den Dekorator auch in die Klasse
#class.py
class makeHtmlTagClass(object):
def __init__(self, tag, css_class=""):
self._tag = tag
self._css_class = " class='{0}'".format(css_class) \
if css_class != "" else ""
def __call__(self, fn):
def wrapped(*args, **kwargs):
return "<" + self._tag + self._css_class+">" \
+ fn(*args, **kwargs) + "</" + self._tag + ">"
return wrapped
@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
return "Hello, {}".format(name)
print hello("Your name")
Der Grund, eine Klasse hier zu mögen, ist, dass es ein eindeutig verwandtes Verhalten gibt, mit zwei Instanzen. Sie können Ihre beiden Dekoratoren tatsächlich erhalten, indem Sie die konstruierten Klassen den gewünschten Namen zuweisen, anstatt die Parameter zu wiederholen. Dies ist bei einer Funktion schwieriger. Das Hinzufügen zu dem Beispiel würde zeigen, warum dies nicht nur redundant ist.
Antwort von Paolo Bergantino hat den großen Vorteil, dass es nur die stdlib verwendet und für dieses einfache Beispiel funktioniert, bei dem es keine Tapezierer Argumente noch dekorierte Funktion Argumente.
Es hat jedoch 3 wesentliche Einschränkungen, wenn man allgemeinere Fälle angehen will:
makestyle(style='bold')
Dekorateur ist nicht trivial.@functools.wraps
die Signatur nicht erhalten Wenn also falsche Argumente angegeben werden, beginnen sie mit der Ausführung und können eine andere Art von Fehler auslösen als die üblichen TypeError
.@functools.wraps
a Zugriff auf ein Argument anhand seines Namens . In der Tat kann das Argument erscheinen in *args
, in **kwargs
oder gar nicht erscheinen (wenn sie fakultativ ist).Ich schrieb decopatch
um das erste Problem zu lösen, und schrieb makefun.wraps
um die beiden anderen zu lösen. Beachten Sie, dass makefun
nutzt denselben Trick wie das berühmte decorator
lib.
Auf diese Weise würden Sie einen Dekorator mit Argumenten erstellen, der wirklich signaturerhaltende Umhüllungen zurückgibt:
from decopatch import function_decorator, DECORATED
from makefun import wraps
@function_decorator
def makestyle(st='b', fn=DECORATED):
open_tag = "<%s>" % st
close_tag = "</%s>" % st
@wraps(fn)
def wrapped(*args, **kwargs):
return open_tag + fn(*args, **kwargs) + close_tag
return wrapped
decopatch
stellt Ihnen zwei weitere Entwicklungsstile zur Verfügung, die die verschiedenen Python-Konzepte je nach Ihren Vorlieben ein- oder ausblenden. Der kompakteste Stil ist der folgende:
from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS
@function_decorator
def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
open_tag = "<%s>" % st
close_tag = "</%s>" % st
return open_tag + fn(*f_args, **f_kwargs) + close_tag
In beiden Fällen können Sie überprüfen, ob der Dekorator wie erwartet funktioniert:
@makestyle
@makestyle('i')
def hello(who):
return "hello %s" % who
assert hello('world') == '<b><i>hello world</i></b>'
Bitte beachten Sie die Dokumentation für Details.
Hier ist ein einfaches Beispiel für die Verkettung von Dekoratoren. Beachten Sie die letzte Zeile - sie zeigt, was unter der Haube vor sich geht.
############################################################
#
# decorators
#
############################################################
def bold(fn):
def decorate():
# surround with bold tags before calling original function
return "<b>" + fn() + "</b>"
return decorate
def uk(fn):
def decorate():
# swap month and day
fields = fn().split('/')
date = fields[1] + "/" + fields[0] + "/" + fields[2]
return date
return decorate
import datetime
def getDate():
now = datetime.datetime.now()
return "%d/%d/%d" % (now.day, now.month, now.year)
@bold
def getBoldDate():
return getDate()
@uk
def getUkDate():
return getDate()
@bold
@uk
def getBoldUkDate():
return getDate()
print getDate()
print getBoldDate()
print getUkDate()
print getBoldUkDate()
# what is happening under the covers
print bold(uk(getDate))()
Die Ausgabe sieht wie folgt aus:
17/6/2013
<b>17/6/2013</b>
6/17/2013
<b>6/17/2013</b>
<b>6/17/2013</b>
def frame_tests(fn):
def wrapper(*args):
print "\nStart: %s" %(fn.__name__)
fn(*args)
print "End: %s\n" %(fn.__name__)
return wrapper
@frame_tests
def test_fn1():
print "This is only a test!"
@frame_tests
def test_fn2(s1):
print "This is only a test! %s" %(s1)
@frame_tests
def test_fn3(s1, s2):
print "This is only a test! %s %s" %(s1, s2)
if __name__ == "__main__":
test_fn1()
test_fn2('OK!')
test_fn3('OK!', 'Just a test!')
Ergebnis:
Start: test_fn1
This is only a test!
End: test_fn1
Start: test_fn2
This is only a test! OK!
End: test_fn2
Start: test_fn3
This is only a test! OK! Just a test!
End: test_fn3
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.