Frage Wie man eine Kette von Funktionsdekoratoren macht?


Wie kann ich zwei Dekoratoren in Python erstellen, die Folgendes tun würden?

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

... die zurückkehren sollte:

"<b><i>Hello</i></b>"

Ich versuche nicht zu machen HTML so in einer realen Anwendung - nur versuchen zu verstehen, wie Dekoratoren und Decorator Verkettung funktioniert.


2377
2018-04-11 07:05


Ursprung


Antworten:


Auschecken die Dokumentation um zu sehen, wie Dekorateure arbeiten. Hier ist, was Sie gefragt haben:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() ## returns "<b><i>hello world</i></b>"

2654
2018-04-11 07:16



Wenn Sie nicht in langen Erklärungen sind, sehen Sie Paolo Bergantinos Antwort.

Decorator-Grundlagen

Pythons Funktionen sind Objekte

Um Dekoratoren zu verstehen, müssen Sie zunächst verstehen, dass Funktionen Objekte in Python sind. Dies hat wichtige Konsequenzen. Mal sehen warum mit einem einfachen Beispiel:

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, e:
    print(e)
    #outputs: "name 'shout' is not defined"

print(scream())
# outputs: 'Yes!'

Behalte dies im Kopf. Wir werden kurz 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, e:
    print(e)
    #outputs : "name 'whisper' is not defined"*
    #Python's functions are objects

Funktionen Referenzen

Okay, immer noch hier? Jetzt der spaßige Teil ...

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

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

Das bedeutet, dass eine Funktion kann return eine andere 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 mehr!

Wenn du kannst return eine Funktion, die man als Parameter übergeben kann:

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, um Dekorateure zu verstehen. Sie sehen, Dekorateure sind "Wrapper", was bedeutet, dass Sie lassen Sie Code vor und nach der Funktion ausführen, die sie dekorieren ohne die Funktion selbst zu verändern.

Handgefertigte Dekorateure

Wie würden Sie es manuell tun:

# 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

Jetzt möchten Sie das wahrscheinlich jedes Mal, wenn Sie anrufen a_stand_alone_function, a_stand_alone_function_decorated wird stattdessen aufgerufen. Das ist einfach, einfach überschreiben a_stand_alone_function mit der Funktion, die von zurückgegeben wird 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!

Decorators entmystifiziert

Das vorherige Beispiel, das die Dekoratorsyntax verwendet:

@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 es. @decorator ist nur eine Verknüpfung zu:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

Dekorateure sind nur eine pythonische Variante des Decorator Entwurfsmuster. In Python sind mehrere klassische Designmuster eingebettet, um die Entwicklung zu erleichtern (wie Iteratoren).

Natürlich können Sie Dekorateure akkumulieren:

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~
#<\______/>

Verwenden der Python-Dekoratorsyntax:

@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

Die Reihenfolge, die Sie den Dekoratoren MATTERS setzen:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print(food)

strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~

Jetzt: um die Frage zu beantworten ...

Als Schlussfolgerung können Sie leicht sehen, wie Sie die Frage beantworten können:

# 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 glücklich gehen, oder Ihr Gehirn ein wenig mehr verbrennen und fortgeschrittene Anwendungen von Dekorateuren sehen.


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

Eine nette Sache über Python ist, dass Methoden und Funktionen wirklich gleich sind. Der einzige Unterschied besteht darin, dass Methoden erwarten, dass ihr erstes Argument eine Referenz auf das aktuelle Objekt ist (self).

Das heißt, Sie können einen Dekorator für Methoden auf die gleiche Weise bauen! Denken Sie daran, zu nehmen self in Betracht:

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

Nun, was würdest du sagen, wenn man Argumente an den Dekorateur selbst weitergibt?

Dies kann etwas verdreht werden, da ein Dekorator eine Funktion als Argument akzeptieren muss. Daher können Sie die Argumente der verzierten Funktion nicht direkt an den Dekorator übergeben.

Bevor wir zur Lösung gehen, schreiben wir eine kleine Erinnerung:

# 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"heißt. Also wenn du @my_decorator, Sie sagen Python, die Funktion "durch die Variable beschriftet" aufzurufenmy_decorator"".

Das ist wichtig! Das Etikett, das Sie angeben, kann direkt auf den Dekorateur zeigen.oder nicht.

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

Keine Überraschung hier.

Lassen Sie uns GENAU dasselbe tun, aber überspringen Sie alle lästigen Zwischenvariablen:

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.

Lass es uns machen 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 Dekoratoren mit Argumenten. Wenn wir Funktionen verwenden können, um den Dekorator im laufenden Betrieb zu erzeugen, 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 es: ein Dekorateur mit Argumenten. Argumente können als Variable festgelegt 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, können Sie Argumente wie jede andere Funktion an den Decorator übergeben. Sie können sogar verwenden *args, **kwargs wenn Sie wünschen. Aber denk daran, Dekorateure werden gerufen nur einmal. Gerade wenn Python das Skript importiert. Sie können die Argumente anschließend nicht dynamisch festlegen. Wenn Sie "x importieren", Die Funktion ist bereits eingerichtetAlso kannst du nicht etwas ändern.


Lass uns üben: einen Dekorateur dekorieren

Okay, als Bonus gebe ich dir ein Schnipsel, damit jeder Dekorateur generisch jedes Argument akzeptieren kann. Um Argumente zu akzeptieren, haben wir unseren Decorator mit einer anderen Funktion erstellt.

Wir wickelten den Dekorateur ein.

Sonst haben wir in letzter Zeit die eingepackte Funktion gesehen?

Oh ja, Dekorateure!

Lass uns Spaß haben und einen Dekorateur für die Dekorateure schreiben:

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

Es 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 du dieses Gefühl hattetest, war es, nachdem du einem Typen zuhörtest, der sagte: "Bevor du die Rekursion verstehst, musst du zuerst die Rekursion verstehen." Aber fühlst du dich nicht gut darin, das zu meistern?


Best Practices: Dekorateure

  • Decorators wurden in Python 2.4 eingeführt, also sei sicher, dass dein Code auf> = 2.4 ausgeführt wird.
  • Dekoratoren verlangsamen den Funktionsaufruf. Merk dir das.
  • Sie können eine Funktion nicht de-dekorieren. (Dort sind hackt, um Dekoratoren zu erstellen, die entfernt werden können, aber niemand benutzt sie.) Sobald eine Funktion eingerichtet ist, ist sie dekoriert für den ganzen Code.
  • Decorators Wrapping-Funktionen, die sie schwer zu debuggen machen können. (Dies wird besser von Python> = 2.5; siehe unten.)

Das 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 den Wrapper kopiert.

(Lustige Tatsache: 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?

Jetzt die große Frage: Wofür kann ich Dekorateure verwenden?

Sieht cool und kraftvoll aus, aber ein praktisches Beispiel wäre großartig. Nun, es gibt 1000 Möglichkeiten. Klassische Verwendungen erweitern ein Funktionsverhalten von einer externen Lib (Sie können es nicht ändern) oder für das Debuggen (Sie möchten es nicht ändern, weil es temporär ist).

Sie können sie verwenden, um mehrere Funktionen auf eine DRY-Art zu erweitern:

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

Natürlich ist die gute Sache mit Dekorateuren, dass Sie sie sofort auf fast alles ohne Umschreiben verwenden können. TROCKEN, 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, etc.

  • Django verwendet Decorators, um Caching- und View-Berechtigungen zu verwalten.
  • Twisted, um asynchrone Funktionsaufrufe zu fälschen.

Das ist wirklich ein großer Spielplatz.


3805
2018-04-11 08:00



Alternativ können Sie eine Factory-Funktion schreiben, die einen Decorator zurückgibt, der den Rückgabewert der dekorierten Funktion in ein Tag einfügt, das an die Factory-Funktion übergeben wird. Beispielsweise:

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

So können Sie schreiben:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

oder

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

Persönlich hätte ich den Dekorateur 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 würde ergeben:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

Vergessen Sie nicht die Konstruktion, für die die Dekoratorsyntax eine Kurzform ist:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

132
2017-10-25 06:18



Es sieht so aus, als ob die anderen Leute Ihnen bereits gesagt haben, wie Sie das Problem lösen können. Ich hoffe, das wird dir helfen zu verstehen, was Dekorateure sind.

Dekorateure sind nur syntaktischer Zucker.

Dies

@decorator
def func():
    ...

expandiert nach

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

100
2018-04-11 07:19



Und natürlich können Sie Lambdas auch von 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()

59
2018-05-17 03:26



Python-Dekoratoren fügen einer anderen Funktion zusätzliche Funktionalität hinzu

Ein kursiv dekorierter könnte wie sein

def makeitalic(fn):
    def newFunc():
        return "<i>" + fn() + "</i>"
    return newFunc

Beachten Sie, dass eine Funktion in einer Funktion definiert ist. Was es im Grunde tut, ist eine Funktion durch die neu definierte zu ersetzen. Zum Beispiel habe ich diese Klasse

class foo:
    def bar(self):
        print "hi"
    def foobar(self):
        print "hi again"

Sagen wir jetzt, ich möchte, dass beide Funktionen "---" nach und vor dem Ausdrucken drucken. Ich könnte einen Druck "---" vor und nach jeder Druckanweisung hinzufügen. Aber weil ich mich nicht gerne wiederhole, werde ich einen Dekorateur machen

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 meine Klasse ändern

class foo:
    @addDashes
    def bar(self):
        print "hi"

    @addDashes
    def foobar(self):
        print "hi again"

Für mehr auf Dekorateure, überprüfen Sie http://www.ibm.com/developerworks/linux/library/l-cpdecor.html


56
2017-12-26 06:13



Sie könnte mache zwei separate Dekorateure, die tun, was du willst, wie direkt unten gezeigt. Beachten Sie die Verwendung von *args, **kwargs in der Erklärung der wrapped() Funktion, die die verzierte Funktion mit mehreren Argumenten unterstützt (was für das Beispiel nicht wirklich notwendig ist) say() Funktion, aber ist für die Allgemeinheit enthalten).

Aus ähnlichen Gründen, die functools.wraps Decorator wird verwendet, um die Meta-Attribute der umhüllten Funktion so zu ändern, dass sie die des dekorierten ist. Dies macht Fehlermeldungen und eingebettete Funktionsdokumentation (func.__doc__) seien Sie die der dekorierten Funktion statt wrapped()ist es.

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>

Verfeinerungen

Wie Sie sehen können, gibt es viel doppelten Code in diesen beiden Dekoratoren. Angesichts dieser Ähnlichkeit wäre es besser für Sie, stattdessen eine generische zu machen, die eigentlich eine war Dekorateur FabrikMit anderen Worten, ein Dekorateur, der andere Dekorateure macht. Auf diese Weise würde es weniger Codewiederholung geben - und das erlauben TROCKEN 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 generierten Dekoratoren einen aussagekräftigeren Namen zuweisen:

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 kombinieren Sie sie sogar so:

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>

Effizienz

Während die obigen Beispiele alle Arbeiten ausführen, beinhaltet der erzeugte Code einen beträchtlichen Overhead in Form von überflüssigen Funktionsaufrufen, wenn mehrere Dekorierer gleichzeitig angewendet werden. Abhängig von der genauen Verwendung (die beispielsweise E / A-gebunden sein kann) spielt dies keine Rolle.

Wenn die Geschwindigkeit der dekorierten Funktion wichtig ist, kann der Overhead zu einem einzigen zusätzlichen Funktionsaufruf gehalten werden, indem eine etwas andere Dekorator-Factory-Funktion geschrieben wird, die das gleichzeitige Hinzufügen aller Tags implementiert, so dass Code generiert werden kann, der die zusätzlichen Funktionsaufrufe vermeidet durch Verwenden separater Dekoratoren für jede Markierung.

Dies erfordert mehr Code im Decorator selbst, aber dieser wird nur ausgeführt, wenn er auf Funktionsdefinitionen angewendet wird, nicht später, wenn er selbst aufgerufen wird. Dies gilt auch, wenn Sie mehr lesbare Namen mit verwenden lambda Funktionen wie zuvor dargestellt. Probe:

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>

25
2017-12-03 18:09



Eine andere Möglichkeit, dasselbe 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, 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'

18
2017-07-26 16:11



Wie kann ich zwei Dekoratoren in Python erstellen, die Folgendes tun würden?

Sie möchten beim Aufruf die folgende Funktion:

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

Zurückgeben:

<b><i>Hello</i></b>

Einfache Lösung

Um dies am einfachsten zu tun, machen Sie Dekoratoren, die lambdas (anonyme Funktionen) zurückgeben, die sich über die Funktion schließen (closures) und rufen sie auf:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

Benutze sie nun wie gewünscht:

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

und nun:

>>> say()
'<b><i>Hello</i></b>'

Probleme mit der einfachen Lösung

Aber wir scheinen die ursprüngliche Funktion fast verloren zu haben.

>>> say
<function <lambda> at 0x4ACFA070>

Um es zu finden, müssten wir die Schließung jedes Lambdas untersuchen, von denen eines in dem anderen begraben 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 Dokumentation zu dieser Funktion hinzufügen oder Funktionen mit mehr als einem Argument dekorieren möchten, oder wir einfach nur wissen wollten, welche Funktion wir in einer Debugging-Sitzung untersucht haben, müssen wir etwas mehr mit unserem tun Verpackung.

Voll ausgestattete Lösung - Überwindung der meisten dieser Probleme

Wir haben den Dekorateur wraps von dem 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 ein wenig Standard gibt, aber das ist so einfach wie wir es schaffen können.

In Python 3 bekommst du auch __qualname__ und __annotations__ standardmäßig zugewiesen.

Also jetzt:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

Und nun:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

Fazit

Das sehen wir also wraps macht die Wrapping-Funktion fast alles, außer uns genau zu sagen, was die Funktion als Argumente nimmt.

Es gibt andere Module, die versuchen, das Problem anzugehen, aber die Lösung ist noch nicht in der Standardbibliothek.


15
2018-03-20 09:48



Ein Decorator nimmt die Funktionsdefinition und erstellt eine neue Funktion, die diese Funktion ausführt und das Ergebnis transformiert.

@deco
def do():
    ...

ist gleichwertig zu:

do = deco(do)

Beispiel:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

Dies

@deco
def do(number):
    return chr(number)  # number to letter

ist äquivalent zu diesem     def do2 (Nummer):         return chr (Nummer)

do2 = deco(do2)

65 <=> 'a'

print(do(65))
print(do2(65))
>>> B
>>> B

Um den Dekorator zu verstehen, ist es wichtig zu bemerken, dass der Dekorator eine neue Funktion erstellt hat, die inner ist, die func ausführt und das Ergebnis transformiert.


10
2018-04-03 09:43