3152 Stimmen

Wie kann man Funktionsdekoratoren erstellen und sie miteinander verketten?

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>"

4780voto

e-satis Punkte 547539

Wenn Sie keine Lust auf lange Erklärungen haben, siehe Antwort von Paolo Bergantino .

Grundlagen der Dekoration

Die Funktionen von Python sind Objekte

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

Funktionen Referenzen

Okay, immer noch hier? Jetzt kommt der lustige Teil...

Sie haben gesehen, dass Funktionen Objekte sind. Daher sind Funktionen:

  • kann einer Variablen zugewiesen werden
  • kann in einer anderen Funktion definiert werden

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.

Handgefertigte Dekorateure

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!

Dekorateure entmystifiziert

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~

Nun zur Beantwortung der Frage...

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.


Die Dekorateure auf die nächste Stufe bringen

Übergabe von Argumenten an die dekorierte Funktion

# 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

Dekorationsmethoden

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?

Übergabe von Argumenten an den Dekorator

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)

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.

Finally:

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.

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.


Üben wir: Dekorieren eines Dekorateurs

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?


Bewährte Praktiken: Dekoratoren

  • Decorators wurden in Python 2.4 eingeführt, also stellen Sie sicher, dass Ihr Code auf >= 2.4 läuft.
  • Dekoratoren verlangsamen den Funktionsaufruf. Behalten Sie das im Hinterkopf.
  • Sie können eine Veranstaltung nicht entdekorieren. (Dort sind Hacks, um Dekoratoren zu erstellen, die entfernt werden können, aber niemand benutzt sie). Sobald also eine Funktion dekoriert ist, ist sie dekoriert für den gesamten Code .
  • Dekoratoren umhüllen Funktionen, was es schwierig machen kann, sie zu debuggen. (Dies wird ab Python >= 2.5 besser; siehe unten.)

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

Wie können die Dekorateure nützlich sein?

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.

  • Django verwendet Dekoratoren, um Caching und View-Berechtigungen zu verwalten.
  • Twisted, um das Inlining von asynchronen Funktionsaufrufen vorzutäuschen.

Dies ist wirklich ein großer Spielplatz.

26 Stimmen

"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.

13 Stimmen

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.

0 Stimmen

Das Einzige, was noch fehlt, ist die Frage, wie man eine Funktion testet, die als Dekorator erstellt wurde (wenn das Sinn macht).

3090voto

Paolo Bergantino Punkte 465120

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>"

273 Stimmen

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).

35 Stimmen

*args y **kwargs sollte in der Antwort hinzugefügt werden. Dekorierte Funktionen können Argumente haben, die verloren gehen, wenn sie nicht angegeben werden.

4 Stimmen

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.

159voto

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)))

8 Stimmen

Meiner Meinung nach ist es besser, mehr als einen Dekorator so weit wie möglich zu vermeiden. Wenn ich eine Fabrikfunktion schreiben müsste, würde ich sie mit *kwargs codieren wie def wrap_in_tag(*kwargs) では @wrap_in_tag('b','i')

146voto

Unknown Punkte 44574

Es sieht so aus, als hätten Ihnen die anderen schon gesagt, wie Sie das Problem lösen können. Ich hoffe, dies hilft Ihnen zu verstehen, was Dekoratoren sind.

Dekoratoren sind nur syntaktischer Zucker.

Diese

@decorator
def func():
    ...

erweitert sich auf

def func():
    ...
func = decorator(func)

4 Stimmen

Das ist so elegant, einfach, leicht zu verstehen. 10000 Upvotes für Sie, Sir Ockham.

7 Stimmen

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

0 Stimmen

@OmerDagan das ist kein syntaktischer Zucker, sondern einfach regulärer Python-Code. In den Generator (nach dem @-Zeichen) können Sie einen regulären Python-Ausdruck einfügen, der eine Dekorator-Funktion ergibt.

74voto

Rune Kaagaard Punkte 6296

Und natürlich kann man auch Lambdas aus einer Decorator-Funktion zurückgeben:

def makebold(f): 
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f): 
    return lambda: "<i>" + f() + "</i>"

@makebold
@makeitalic
def say():
    return "Hello"

print say()

13 Stimmen

Und einen Schritt weiter: makebold = lambda f : lambda "<b>" + f() + "</b>"

16 Stimmen

@Rob: Um syntaktisch korrekt zu sein: makebold = lambda f: lambda: "<b>" + f() + "</b>"

13 Stimmen

Ich komme zwar zu spät, aber ich würde wirklich vorschlagen makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"

CodeJaeger.com

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.

Powered by:

X