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>"
Wenn Sie keine Lust auf lange Erklärungen haben, siehe Antwort von Paolo Bergantino .
Um Dekoratoren zu verstehen, müssen Sie zuerst verstehen, dass Funktionen in Python Objekte sind. Das hat wichtige Konsequenzen. Lassen Sie uns anhand eines einfachen Beispiels sehen, warum:
def shout(word="yes"):
return word.capitalize()+"!"
print(shout())
# outputs : 'Yes!'
# As an object, you can assign the function to a variable like any other object
scream = shout
# Notice we don't use parentheses: we are not calling the function,
# we are putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream":
print(scream())
# outputs : 'Yes!'
# More than that, it means you can remove the old name 'shout',
# and the function will still be accessible from 'scream'
del shout
try:
print(shout())
except NameError as e:
print(e)
#outputs: "name 'shout' is not defined"
print(scream())
# outputs: 'Yes!'
Behalten Sie dies im Hinterkopf. Wir werden in Kürze darauf zurückkommen.
Eine weitere interessante Eigenschaft von Python-Funktionen ist, dass sie innerhalb einer anderen Funktion definiert werden können!
def talk():
# You can define a function on the fly in "talk" ...
def whisper(word="yes"):
return word.lower()+"..."
# ... and use it right away!
print(whisper())
# You call "talk", that defines "whisper" EVERY TIME you call it, then
# "whisper" is called in "talk".
talk()
# outputs:
# "yes..."
# But "whisper" DOES NOT EXIST outside "talk":
try:
print(whisper())
except NameError as e:
print(e)
#outputs : "name 'whisper' is not defined"*
#Python's functions are objects
Okay, immer noch hier? Jetzt kommt der lustige Teil...
Sie haben gesehen, dass Funktionen Objekte sind. Daher sind Funktionen:
Das bedeutet, dass eine Funktion kann return
weitere Funktion .
def getTalk(kind="shout"):
# We define functions on the fly
def shout(word="yes"):
return word.capitalize()+"!"
def whisper(word="yes") :
return word.lower()+"..."
# Then we return one of them
if kind == "shout":
# We don't use "()", we are not calling the function,
# we are returning the function object
return shout
else:
return whisper
# How do you use this strange beast?
# Get the function and assign it to a variable
talk = getTalk()
# You can see that "talk" is here a function object:
print(talk)
#outputs : <function shout at 0xb7ea817c>
# The object is the one returned by the function:
print(talk())
#outputs : Yes!
# And you can even use it directly if you feel wild:
print(getTalk("whisper")())
#outputs : yes...
Es gibt noch mehr!
Wenn Sie können return
eine Funktion, können Sie einen als Parameter übergeben:
def doSomethingBefore(func):
print("I do something before then I call the function you gave me")
print(func())
doSomethingBefore(scream)
#outputs:
#I do something before then I call the function you gave me
#Yes!
Nun, Sie haben einfach alles, was man braucht, um Dekorateure zu verstehen. Sie sehen, Dekoratoren sind "Wrapper", was bedeutet, dass Sie ermöglichen die Ausführung von Code vor und nach der Funktion, die sie schmücken ohne die Funktion selbst zu ändern.
Wie man es manuell macht:
# A decorator is a function that expects ANOTHER function as parameter
def my_shiny_new_decorator(a_function_to_decorate):
# Inside, the decorator defines a function on the fly: the wrapper.
# This function is going to be wrapped around the original function
# so it can execute code before and after it.
def the_wrapper_around_the_original_function():
# Put here the code you want to be executed BEFORE the original function is called
print("Before the function runs")
# Call the function here (using parentheses)
a_function_to_decorate()
# Put here the code you want to be executed AFTER the original function is called
print("After the function runs")
# At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
# We return the wrapper function we have just created.
# The wrapper contains the function and the code to execute before and after. It’s ready to use!
return the_wrapper_around_the_original_function
# Now imagine you create a function you don't want to ever touch again.
def a_stand_alone_function():
print("I am a stand alone function, don't you dare modify me")
a_stand_alone_function()
#outputs: I am a stand alone function, don't you dare modify me
# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in
# any code you want and return you a new function ready to be used:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
Das wollen Sie wahrscheinlich jedes Mal, wenn Sie anrufen. a_stand_alone_function
, a_stand_alone_function_decorated
wird stattdessen aufgerufen. Das ist einfach, überschreiben Sie einfach a_stand_alone_function
mit der Funktion, die von my_shiny_new_decorator
:
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# That’s EXACTLY what decorators do!
Das vorangegangene Beispiel unter Verwendung der Decorator-Syntax:
@my_shiny_new_decorator
def another_stand_alone_function():
print("Leave me alone")
another_stand_alone_function()
#outputs:
#Before the function runs
#Leave me alone
#After the function runs
Ja, das ist alles, so einfach ist das. @decorator
ist nur eine Abkürzung zu:
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
Dekoratoren sind lediglich eine pythonische Variante der Dekorator-Entwurfsmuster . Es gibt mehrere klassische Entwurfsmuster, die in Python eingebettet sind und die Entwicklung erleichtern (z. B. Iteratoren).
Natürlich können Sie auch Dekorateure anwerben:
def bread(func):
def wrapper():
print("</''''''\>")
func()
print("<\______/>")
return wrapper
def ingredients(func):
def wrapper():
print("#tomatoes#")
func()
print("~salad~")
return wrapper
def sandwich(food="--ham--"):
print(food)
sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
Verwendung der Python-Dekorator-Syntax:
@bread
@ingredients
def sandwich(food="--ham--"):
print(food)
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
Die Reihenfolge, in der Sie die Dekoratoren setzen, ist von Bedeutung:
@ingredients
@bread
def strange_sandwich(food="--ham--"):
print(food)
strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~
Als Schlussfolgerung können Sie leicht erkennen, wie die Frage zu beantworten ist:
# The decorator to make it bold
def makebold(fn):
# The new function the decorator returns
def wrapper():
# Insertion of some code before and after
return "<b>" + fn() + "</b>"
return wrapper
# The decorator to make it italic
def makeitalic(fn):
# The new function the decorator returns
def wrapper():
# Insertion of some code before and after
return "<i>" + fn() + "</i>"
return wrapper
@makebold
@makeitalic
def say():
return "hello"
print(say())
#outputs: <b><i>hello</i></b>
# This is the exact equivalent to
def say():
return "hello"
say = makebold(makeitalic(say))
print(say())
#outputs: <b><i>hello</i></b>
Sie können jetzt einfach zufrieden abreisen oder sich noch ein bisschen mehr den Kopf zerbrechen und die fortgeschrittenen Verwendungsmöglichkeiten von Dekorateuren kennenlernen.
# It’s not black magic, you just have to let the wrapper
# pass the argument:
def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print("I got args! Look: {0}, {1}".format(arg1, arg2))
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments
# Since when you are calling the function returned by the decorator, you are
# calling the wrapper, passing arguments to the wrapper will let it pass them to
# the decorated function
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print("My name is {0} {1}".format(first_name, last_name))
print_full_name("Peter", "Venkman")
# outputs:
#I got args! Look: Peter Venkman
#My name is Peter Venkman
Ein Vorteil von Python ist, dass Methoden und Funktionen eigentlich dasselbe sind. Der einzige Unterschied ist, dass Methoden erwarten, dass ihr erstes Argument ein Verweis auf das aktuelle Objekt ist ( self
).
Das bedeutet, dass Sie auf die gleiche Weise einen Dekorator für Methoden erstellen können! Denken Sie nur daran, die self
zu berücksichtigen:
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # very friendly, decrease age even more :-)
return method_to_decorate(self, lie)
return wrapper
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def sayYourAge(self, lie):
print("I am {0}, what did you think?".format(self.age + lie))
l = Lucy()
l.sayYourAge(-3)
#outputs: I am 26, what did you think?
Wenn Sie einen Allzweck-Dekorator erstellen - einen, den Sie auf jede Funktion oder Methode anwenden, unabhängig von ihren Argumenten -, dann verwenden Sie einfach *args, **kwargs
:
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
# The wrapper accepts any arguments
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print("Do I have args?:")
print(args)
print(kwargs)
# Then you unpack the arguments, here *args, **kwargs
# If you are not familiar with unpacking, check:
# http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
function_to_decorate(*args, **kwargs)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print("Python is cool, no argument here.")
function_with_no_argument()
#outputs
#Do I have args?:
#()
#{}
#Python is cool, no argument here.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print(a, b, c)
function_with_arguments(1,2,3)
#outputs
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
print("Do {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus))
function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#outputs
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!
class Mary(object):
def __init__(self):
self.age = 31
@a_decorator_passing_arbitrary_arguments
def sayYourAge(self, lie=-3): # You can now add a default value
print("I am {0}, what did you think?".format(self.age + lie))
m = Mary()
m.sayYourAge()
#outputs
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?
Großartig, was würden Sie jetzt über die Übergabe von Argumenten an den Dekorator selbst sagen?
Dies kann etwas verwirrend sein, da ein Dekorator eine Funktion als Argument akzeptieren muss. Daher können Sie die Argumente der dekorierten Funktion nicht direkt an den Dekorator übergeben.
Bevor wir uns auf die Lösung stürzen, sollten wir eine kleine Erinnerung schreiben:
# Decorators are ORDINARY functions
def my_decorator(func):
print("I am an ordinary function")
def wrapper():
print("I am function returned by the decorator")
func()
return wrapper
# Therefore, you can call it without any "@"
def lazy_function():
print("zzzzzzzz")
decorated_function = my_decorator(lazy_function)
#outputs: I am an ordinary function
# It outputs "I am an ordinary function", because that’s just what you do:
# calling a function. Nothing magic.
@my_decorator
def lazy_function():
print("zzzzzzzz")
#outputs: I am an ordinary function
Es ist genau dasselbe. " my_decorator
" genannt wird. Wenn Sie also @my_decorator
sagen Sie Python, dass es die Funktion aufrufen soll, die durch die Variable " my_decorator
"'.
Das ist wichtig! Das Etikett, das Sie vergeben, kann direkt auf den Dekorateur verweisen. oder nicht .
Lasst uns böse werden.
def decorator_maker():
print("I make decorators! I am executed only once: "
"when you make me create a decorator.")
def my_decorator(func):
print("I am a decorator! I am executed only when you decorate a function.")
def wrapped():
print("I am the wrapper around the decorated function. "
"I am called when you call the decorated function. "
"As the wrapper, I return the RESULT of the decorated function.")
return func()
print("As the decorator, I return the wrapped function.")
return wrapped
print("As a decorator maker, I return a decorator")
return my_decorator
# Let’s create a decorator. It’s just a new function after all.
new_decorator = decorator_maker()
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
# Then we decorate the function
def decorated_function():
print("I am the decorated function.")
decorated_function = new_decorator(decorated_function)
#outputs:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function
# Let’s call the function:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.
Das ist keine Überraschung.
Wir machen GENAU dasselbe, lassen aber die ganzen lästigen Zwischenvariablen weg:
def decorated_function(): print("I am the decorated function.") decorated_function = decorator_maker()(decorated_function)
decorated_function()
Machen wir es noch kürzer :
@decorator_maker()
def decorated_function():
print("I am the decorated function.")
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.
#Eventually:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.
Hey, hast du das gesehen? Wir haben einen Funktionsaufruf mit dem " @
" Syntax! :-)
Also, zurück zu den Dekorateuren mit Argumenten. Wenn wir Funktionen verwenden können, um den Dekorator on the fly zu generieren, können wir Argumente an diese Funktion übergeben, richtig?
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))
def my_decorator(func):
# The ability to pass arguments here is a gift from closures.
# If you are not comfortable with closures, you can assume it’s ok,
# or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))
# Don't confuse decorator arguments and function arguments!
def wrapped(function_arg1, function_arg2) :
print("I am the wrapper around the decorated function.\n"
"I can access all the variables\n"
"\t- from the decorator: {0} {1}\n"
"\t- from the function call: {2} {3}\n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapped
return my_decorator
@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard
Hier ist er: ein Dekorator mit Argumenten. Argumente können als Variable gesetzt werden:
c1 = "Penny"
c2 = "Leslie"
@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
print("I am the decorated function and only knows about my arguments:"
" {0} {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments(c2, "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Penny
# - from the function call: Leslie Howard
#Then I can pass them to the decorated function
#I am the decorated function and only know about my arguments: Leslie Howard
Wie Sie sehen, können Sie dem Dekorator mit diesem Trick Argumente wie jeder anderen Funktion übergeben. Sie können sogar *args, **kwargs
wenn Sie es wünschen. Aber denken Sie daran, dass Dekorateure genannt werden nur einmal . Nur wenn Python das Skript importiert. Sie können die Argumente nicht nachträglich dynamisch setzen. Wenn Sie "import x" machen, die Funktion ist bereits dekoriert Sie können also nicht nichts ändern.
Okay, als Bonus gebe ich Ihnen ein Snippet, das jeden Dekorator dazu bringt, generisch jedes Argument zu akzeptieren. Schließlich haben wir, um Argumente zu akzeptieren, unseren Dekorator mit einer anderen Funktion erstellt.
Wir haben den Dekorateur eingewickelt.
Gibt es sonst noch etwas, das wir in letzter Zeit gesehen haben, das funktioniert hat?
Oh ja, Dekorateure!
Machen wir uns einen Spaß und schreiben wir einen Dekorateur für die Dekorateure:
def decorator_with_args(decorator_to_enhance):
"""
This function is supposed to be used as a decorator.
It must decorate an other function, that is intended to be used as a decorator.
Take a cup of coffee.
It will allow any decorator to accept an arbitrary number of arguments,
saving you the headache to remember how to do that every time.
"""
# We use the same trick we did to pass arguments
def decorator_maker(*args, **kwargs):
# We create on the fly a decorator that accepts only a function
# but keeps the passed arguments from the maker.
def decorator_wrapper(func):
# We return the result of the original decorator, which, after all,
# IS JUST AN ORDINARY FUNCTION (which returns a function).
# Only pitfall: the decorator must have this specific signature or it won't work:
return decorator_to_enhance(func, *args, **kwargs)
return decorator_wrapper
return decorator_maker
Sie kann wie folgt verwendet werden:
# You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "decorator(func, *args, **kwargs)"
@decorator_with_args
def decorated_decorator(func, *args, **kwargs):
def wrapper(function_arg1, function_arg2):
print("Decorated with {0} {1}".format(args, kwargs))
return func(function_arg1, function_arg2)
return wrapper
# Then you decorate the functions you wish with your brand new decorated decorator.
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print("Hello {0} {1}".format(function_arg1, function_arg2))
decorated_function("Universe and", "everything")
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything
# Whoooot!
Ich weiß, das letzte Mal, als Sie dieses Gefühl hatten, war es, nachdem Sie einen Mann sagen hörten: "Bevor man die Rekursion versteht, muss man zuerst die Rekursion verstehen". Aber haben Sie jetzt nicht ein gutes Gefühl, wenn Sie das beherrschen?
El functools
Modul wurde in Python 2.5 eingeführt. Es enthält die Funktion functools.wraps()
die den Namen, das Modul und den Docstring der dekorierten Funktion in ihren Wrapper kopiert.
(Spaßfakt: functools.wraps()
ist ein Dekorateur! )
# For debugging, the stacktrace prints you the function __name__
def foo():
print("foo")
print(foo.__name__)
#outputs: foo
# With a decorator, it gets messy
def bar(func):
def wrapper():
print("bar")
return func()
return wrapper
@bar
def foo():
print("foo")
print(foo.__name__)
#outputs: wrapper
# "functools" can help for that
import functools
def bar(func):
# We say that "wrapper", is wrapping "func"
# and the magic begins
@functools.wraps(func)
def wrapper():
print("bar")
return func()
return wrapper
@bar
def foo():
print("foo")
print(foo.__name__)
#outputs: foo
Nun die große Frage: Wozu kann ich Dekorateure verwenden?
Sie scheinen cool und leistungsstark zu sein, aber ein praktisches Beispiel wäre toll. Nun, es gibt 1000 Möglichkeiten. Klassische Anwendungen sind die Erweiterung eines Funktionsverhaltens von einer externen Lib (Sie können es nicht ändern), oder für die Fehlersuche (Sie wollen es nicht ändern, weil es vorübergehend ist).
Sie können sie verwenden, um mehrere Funktionen auf eine DRY-Art zu erweitern, etwa so:
def benchmark(func):
"""
A decorator that prints the time a function takes
to execute.
"""
import time
def wrapper(*args, **kwargs):
t = time.clock()
res = func(*args, **kwargs)
print("{0} {1}".format(func.__name__, time.clock()-t))
return res
return wrapper
def logging(func):
"""
A decorator that logs the activity of the script.
(it actually just prints it, but it could be logging!)
"""
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print("{0} {1} {2}".format(func.__name__, args, kwargs))
return res
return wrapper
def counter(func):
"""
A decorator that counts and prints the number of times a function has been executed
"""
def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(*args, **kwargs)
print("{0} has been used: {1}x".format(func.__name__, wrapper.count))
return res
wrapper.count = 0
return wrapper
@counter
@benchmark
@logging
def reverse_string(string):
return str(reversed(string))
print(reverse_string("Able was I ere I saw Elba"))
print(reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!"))
#outputs:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A
Das Gute an den Dekoratoren ist natürlich, dass man sie sofort auf fast alles anwenden kann, ohne es umzuschreiben. DRY, sagte ich:
@counter
@benchmark
@logging
def get_random_futurama_quote():
from urllib import urlopen
result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
try:
value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
return value.strip()
except:
return "No, I'm ... doesn't!"
print(get_random_futurama_quote())
print(get_random_futurama_quote())
#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!
Python selbst bietet mehrere Dekoratoren: property
, staticmethod
, usw.
Dies ist wirklich ein großer Spielplatz.
"Man kann eine Funktion nicht entdekorieren." - Obwohl dies normalerweise wahr ist, ist es möglich, innerhalb der Schließung in der Funktionsrückgabe durch einen Dekorator zu gelangen (d.h. über seine __closure__
Attribut), um die ursprüngliche undekorierte Funktion herauszuziehen. Ein Anwendungsbeispiel ist dokumentiert in diese Antwort in dem beschrieben wird, wie es möglich ist, unter bestimmten Umständen eine Dekoratorfunktion in eine niedrigere Ebene zu injizieren.
Das ist zwar eine großartige Antwort, aber ich denke, sie ist in gewisser Weise irreführend. Pythons @decorator
Syntax wird wahrscheinlich am häufigsten verwendet, um eine Funktion durch eine Wrapper-Closure zu ersetzen (wie in der Antwort beschrieben). Sie kann aber auch die Funktion durch etwas anderes ersetzen. Die eingebaute property
, classmethod
y staticmethod
Dekorateure ersetzen die Funktion zum Beispiel durch einen Deskriptor. Ein Dekorator kann auch etwas mit einer Funktion machen, wie z.B. einen Verweis auf sie in einer Art Registry speichern und sie dann unverändert und ohne Wrapper zurückgeben.
Das Einzige, was noch fehlt, ist die Frage, wie man eine Funktion testet, die als Dekorator erstellt wurde (wenn das Sinn macht).
Auschecken die Dokumentation um zu sehen, wie Dekorateure funktionieren. Hier ist, worum Sie gebeten haben:
from functools import wraps
def makebold(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapper
def makeitalic(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</i>"
return wrapper
@makebold
@makeitalic
def hello():
return "hello world"
@makebold
@makeitalic
def log(s):
return s
print hello() # returns "<b><i>hello world</i></b>"
print hello.__name__ # with functools.wraps() this returns "hello"
print log('hello') # returns "<b><i>hello</i></b>"
Erwägen Sie die Verwendung von functools.wraps oder, besser noch, die Dekorationsmodul von PyPI : Sie bewahren bestimmte wichtige Metadaten (wie __name__
und, was das Dekorator-Paket betrifft, die Funktionssignatur).
*args
y **kwargs
sollte in der Antwort hinzugefügt werden. Dekorierte Funktionen können Argumente haben, die verloren gehen, wenn sie nicht angegeben werden.
Obwohl diese Antwort den großen Vorteil hat, dass sie nur die stdlib verwendet und für dieses einfache Beispiel funktioniert, in dem es keine Tapezierer Argumente noch dekorierte Funktion Argumente, hat es 3 wesentliche Einschränkungen: (1) keine einfache Unterstützung für optionale Dekorator-Argumente (2) nicht signaturerhaltend (3) keine einfache Möglichkeit, ein benanntes Argument aus *args
, **kwargs
. Eine einfache Möglichkeit, diese 3 Probleme auf einmal zu lösen, ist die Verwendung von decopatch
wie erklärt aquí . Sie können auch verwenden decorator
wie bereits von Marius Gedminas erwähnt, um die Punkte 2 und 3 zu lösen.
Alternativ könnten Sie eine Fabrikfunktion schreiben, die einen Dekorator zurückgibt, der den Rückgabewert der dekorierten Funktion in ein an die Fabrikfunktion übergebenes Tag verpackt. Zum Beispiel:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator():
return '<%(tag)s>%(rv)s</%(tag)s>' % (
{'tag': tag, 'rv': func()})
return decorator
return factory
Dies ermöglicht Ihnen zu schreiben:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
return 'hello'
o
makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')
@makebold
@makeitalic
def say():
return 'hello'
Ich persönlich hätte den Dekorator etwas anders geschrieben:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator(val):
return func('<%(tag)s>%(val)s</%(tag)s>' %
{'tag': tag, 'val': val})
return decorator
return factory
was zur Folge hätte:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
return val
say('hello')
Vergessen Sie nicht die Konstruktion, für die die Decorator-Syntax eine Kurzform ist:
say = wrap_in_tag('b')(wrap_in_tag('i')(say)))
Großartige und einfache Antwort. Möchte hinzufügen, dass bei der Verwendung von @decorator()
(anstelle von @decorator
) ist es syntaktischer Zucker für func = decorator()(func)
. Dies ist auch gängige Praxis, wenn Sie Dekoratoren "on the fly" erzeugen müssen
Ich komme zwar zu spät, aber ich würde wirklich vorschlagen makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
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.