首页 文章

如何制作一系列功能装饰器?

提问于
浏览
2458

如何在Python中创建两个装饰器来执行以下操作?

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

...应该返回:

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

我不是试图在一个真实的应用程序中以这种方式制作 HTML - 只是想了解装饰器和装饰器链是如何工作的 .

16 回答

  • 3905

    查看the documentation以了解装饰器的工作原理 . 这是你要求的:

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

    如果您没有详细解释,请参阅Paolo Bergantino’s answer .

    装饰器基础知识

    Python的功能是对象

    要理解装饰器,首先必须了解函数是Python中的对象 . 这具有重要的后果 . 让我们通过一个简单的例子来看看为什么:

    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!'
    

    记住这一点 . 我们很快就会回过头来 .

    Python函数的另一个有趣的属性是它们可以在另一个函数中定义!

    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
    

    函数参考

    好的,还在吗?现在有趣的部分......

    你已经看到函数是对象 . 因此,功能:

    • 可以分配给变量

    • 可以在另一个函数中定义

    这意味着 a function can return another function .

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

    还有更多!

    如果你可以 return 一个函数,你可以传递一个作为参数:

    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!
    

    好吧,你只需要了解装饰器所需的一切 . 你看,装饰器是“包装器”,这意味着 they let you execute code before and after the function they decorate 没有修改功能本身 .

    手工装饰

    你是如何手动完成的:

    # 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
    

    现在,您可能希望每次调用 a_stand_alone_function 时,都会调用 a_stand_alone_function_decorated . 这很简单,只需用 my_shiny_new_decorator 返回的函数覆盖 a_stand_alone_function

    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!
    

    装饰者神秘化了

    上一个示例,使用装饰器语法:

    @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
    

    是的,就是这样,就这么简单 . @decorator 只是一个快捷方式:

    another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
    

    装饰者只是decorator design pattern的pythonic变种 . Python中嵌入了几种经典设计模式以简化开发(如迭代器) .

    当然,你可以积累装饰器:

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

    使用Python装饰器语法:

    @bread
    @ingredients
    def sandwich(food="--ham--"):
        print(food)
    
    sandwich()
    #outputs:
    #</''''''\>
    # #tomatoes#
    # --ham--
    # ~salad~
    #<\______/>
    

    您设置装饰器MATTERS的顺序:

    @ingredients
    @bread
    def strange_sandwich(food="--ham--"):
        print(food)
    
    strange_sandwich()
    #outputs:
    ##tomatoes#
    #</''''''\>
    # --ham--
    #<\______/>
    # ~salad~
    

    现在:回答这个问题......

    总之,您可以轻松地看到如何回答这个问题:

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

    你现在可以离开快乐,或者更多地燃烧你的大脑并看到装饰器的高级用途 .


    将装饰器提升到新的水平

    将参数传递给修饰函数

    # 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
    

    装饰方法

    关于Python的一个好消息是方法和函数真的是一样的 . 唯一的区别是方法期望它们的第一个参数是对当前对象的引用( self ) .

    这意味着您可以以相同的方式为方法构建装饰器!请记住考虑 self

    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?
    

    如果您正在制作通用装饰器 - 您将应用于任何函数或方法,无论其参数如何 - 那么只需使用 *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?
    

    将参数传递给装饰器

    好的,现在您对将参数传递给装饰器本身有什么看法?

    这可能会有些扭曲,因为装饰器必须接受函数作为参数 . 因此,您无法将装饰函数的参数直接传递给装饰器 .

    在急于解决之前,让我们写一点提醒:

    # 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
    

    它完全一样 . “ my_decorator ”被称为 . 所以当你 @my_decorator 时,你告诉Python调用函数'由变量标记“ my_decorator ”' .

    这个很重要!您提供的标签可以直接指向装饰器 or not .

    让我们变得邪恶 . ☺

    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.
    

    这里不足为奇 .

    让我们做同样的事情,但跳过所有讨厌的中间变量:

    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.
    

    让它更短:

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

    嘿,你看到了吗?我们使用了一个带有“ @ ”语法的函数调用! :-)

    所以,回到带有参数的装饰器 . 如果我们可以使用函数动态生成装饰器,我们可以将参数传递给该函数,对吧?

    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
    

    这是:带参数的装饰器 . 参数可以设置为变量:

    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
    

    如您所见,您可以像使用此技巧的任何函数一样将参数传递给装饰器 . 如果您愿意,甚至可以使用 *args, **kwargs . 但请记住装饰器被称为 only once . 就在Python导入脚本的时候 . 之后您无法动态设置参数 . 当你执行"import x", the function is already decorated 时,你无法改变任何东西 .


    让我们练习:装饰装饰

    好吧,作为奖励,我会给你一个片段,让任何装饰者一般都接受任何争论 . 毕竟,为了接受参数,我们使用另一个函数创建了装饰器 .

    我们包装了装饰者 .

    我们最近看到的其他包装功能还有什么?

    哦,是的,装饰者!

    让我们玩得开心,为装饰者写一个装饰器:

    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
    

    它可以使用如下:

    # 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!
    

    我知道,你最后一次有这种感觉,是在听了一个人说:“在理解递归之前,你必须先了解递归“ . 但是现在,你不觉得掌握这个吗?


    最佳实践:装饰者

    • 装饰器是在Python 2.4中引入的,因此请确保您的代码将在> = 2.4上运行 .

    • 装饰器减慢了函数调用 . 记在脑子里 .

    • You cannot un-decorate a function. (有一些黑客可以创建可以移除的装饰器,但没有人使用它们 . )因此,一旦一个函数被装饰,它就会为所有代码进行装饰 .

    • 装饰器包装函数,这使得它们难以调试 . (这从Python> = 2.5变得更好;见下文 . )

    functools 模块是在Python 2.5中引入的 . 它包含函数 functools.wraps() ,它将装饰函数的名称,模块和文档字符串复制到其包装器中 .

    (有趣的事实: functools.wraps() 是装饰者!☺)

    # 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
    

    装饰器如何有用?

    Now the big question: 我可以使用装饰器吗?

    看起来酷而有力,但一个实际的例子会很棒 . 嗯,有1000种可能性 . 经典用法是从外部库扩展函数行为(您无法修改它),或者用于调试(您不想修改它,因为它是临时的) .

    您可以使用它们以DRY的方式扩展多个功能,如下所示:

    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
    

    当然,装饰器的好处在于你几乎可以在没有重写的情况下立即使用它们 . 干,我说:

    @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本身提供了几个装饰器: propertystaticmethod 等 .

    • Django使用装饰器来管理缓存和查看权限 .

    • 扭曲伪造内联异步函数调用 .

    这真的是一个大型游乐场 .

  • 3

    您可以制作两个单独的装饰器,它们可以执行您想要的操作,如下图所示 . 注意在 wrapped() 函数的声明中使用 *args, **kwargs ,该函数支持具有多个参数的修饰函数(对于示例 say() 函数来说,这不是必需的,但为了通用性而包括在内) .

    出于类似的原因, functools.wraps 装饰器用于将包装函数的元属性更改为正在装饰的元属性 . 这使得错误消息和嵌入式函数文档( func.__doc__ )成为装饰函数的函数而不是 wrapped() .

    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>
    

    改进

    正如您所看到的,这两个装饰器中存在大量重复代码 . 鉴于这种相似性,你最好制作一个实际上是装饰工厂的通用工具 - 换句话说,是一个制作其他装饰器的装饰器 . 这样就可以减少代码重复次数 - 并允许遵循DRY原则 .

    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>
    

    为了使代码更具可读性,您可以为工厂生成的装饰器分配更具描述性的名称:

    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>
    

    或者甚至将它们组合成这样:

    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>
    

    效率

    虽然上面的示例都可以正常工作,但是当一次应用多个装饰器时,生成的代码会以无关函数调用的形式涉及相当大的开销 . 这可能无关紧要,具体取决于具体用法(例如,可能是I / O绑定) .

    如果修饰函数的速度很重要,可以通过编写稍微不同的装饰器工厂函数来保持一个额外的函数调用,该函数实现一次添加所有标记,因此它可以生成代码以避免发生的附加函数调用通过为每个标签使用单独的装饰器 .

    这需要装饰器本身有更多的代码,但这只在它被应用于函数定义时运行,而不是在它们自己被调用时运行 . 当使用前面所示的 lambda 函数创建更易读的名称时,这也适用 . 样品:

    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>
    
  • 8

    或者,您可以编写一个返回装饰器的工厂函数,该装饰器将装饰函数的返回值包装在传递给工厂函数的标记中 . 例如:

    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
    

    这使您可以写:

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

    要么

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

    就个人而言,我会以不同的方式编写装饰器:

    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
    

    会产生:

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

    不要忘记装饰器语法是简写的结构:

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

    看起来其他人已经告诉过你如何解决这个问题 . 我希望这能帮助你理解装饰器是什么 .

    装饰者只是语法糖 .

    这个

    @decorator
    def func():
        ...
    

    扩展到

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

    这是一个链接装饰器的简单示例 . 注意最后一行 - 它显示了幕后发生的事情 .

    ############################################################
    #
    #    decorators
    #
    ############################################################
    
    def bold(fn):
        def decorate():
            # surround with bold tags before calling original function
            return "<b>" + fn() + "</b>"
        return decorate
    
    
    def uk(fn):
        def decorate():
            # swap month and day
            fields = fn().split('/')
            date = fields[1] + "/" + fields[0] + "/" + fields[2]
            return date
        return decorate
    
    import datetime
    def getDate():
        now = datetime.datetime.now()
        return "%d/%d/%d" % (now.day, now.month, now.year)
    
    @bold
    def getBoldDate(): 
        return getDate()
    
    @uk
    def getUkDate():
        return getDate()
    
    @bold
    @uk
    def getBoldUkDate():
        return getDate()
    
    
    print getDate()
    print getBoldDate()
    print getUkDate()
    print getBoldUkDate()
    # what is happening under the covers
    print bold(uk(getDate))()
    

    输出如下:

    17/6/2013
    <b>17/6/2013</b>
    6/17/2013
    <b>6/17/2013</b>
    <b>6/17/2013</b>
    
  • 2

    Python装饰器为另一个函数添加了额外的功能

    斜体装饰器可能就像

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

    请注意,函数是在函数内定义的 . 它基本上做的是用新定义的函数替换函数 . 例如,我有这门课

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

    现在说,我希望两个函数在完成之后和之前打印“---” . 我可以在每个print语句之前和之后添加一个打印“---” . 但因为我不喜欢重复自己,我会做一个装饰

    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
    

    所以现在我可以改变我的课程

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

    有关装饰器的更多信息,请查看http://www.ibm.com/developerworks/linux/library/l-cpdecor.html

  • 25

    如何在Python中创建两个装饰器来执行以下操作?

    调用时,您需要以下函数:

    @makebold
    @makeitalic
    def say():
    返回“你好”

    回来:

    <b> <i>您好</ i> </ b>

    简单的解决方案

    为了最简单地做到这一点,让make decorators返回关闭函数(闭包)并调用它的lambdas(匿名函数)并调用它:

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

    现在根据需要使用它们:

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

    现在:

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

    简单解决方案的问题

    但我们似乎几乎失去了原有的功能 .

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

    为了找到它,我们需要挖掘每个lambda的闭包,其中一个被埋在另一个中:

    >>> say.__closure__[0].cell_contents
    <function <lambda> at 0x4ACFA030>
    >>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
    <function say at 0x4ACFA730>
    

    因此,如果我们将文档放在这个函数上,或者希望能够装饰带有多个参数的函数,或者我们只是想知道我们在调试会话中看到了什么函数,我们需要对我们的函数做更多的事情 . 包装 .

    全功能解决方案 - 克服大多数这些问题

    我们有标准库中 functools 模块的装饰器 wraps

    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
    

    不幸的是,仍然有一些样板,但这很简单,我们可以做到 .

    在Python 3中,默认情况下还会分配 __qualname____annotations__ .

    所以现在:

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

    现在:

    >>> say
    <function say at 0x14BB8F70>
    >>> help(say)
    Help on function say in module __main__:
    
    say(*args, **kwargs)
        This function returns a bolded, italicized 'hello'
    

    结论

    所以我们看到 wraps 使包装函数几乎完成所有操作,除了告诉我们函数作为参数的确切内容 .

    还有其他模块可能尝试解决该问题,但该解决方案尚未出现在标准库中 .

  • 4

    说到计数器示例 - 如上所述,计数器将在使用装饰器的所有函数之间共享:

    def counter(func):
        def wrapped(*args, **kws):
            print 'Called #%i' % wrapped.count
            wrapped.count += 1
            return func(*args, **kws)
        wrapped.count = 0
        return wrapped
    

    这样,您的装饰器可以重复用于不同的函数(或用于多次装饰相同的函数: func_counter1 = counter(func); func_counter2 = counter(func) ),并且计数器变量将保持对每个函数都是私有的 .

  • 17

    另一种做同样事情的方法:

    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'
    

    或者,更灵活:

    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'
    
  • 2709

    装饰器接受函数定义并创建一个执行此函数并转换结果的新函数 .

    @deco
    def do():
        ...
    

    相当于:

    do = deco(do)
    

    示例:

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

    这个

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

    与此def do2(数字)等效:return chr(number)

    do2 = deco(do2)
    

    65 <=>'a'

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

    要理解装饰器,重要的是要注意,装饰器创建了一个新的函数do,它执行func并转换结果 .

  • 1

    用不同数量的参数装饰函数:

    def frame_tests(fn):
        def wrapper(*args):
            print "\nStart: %s" %(fn.__name__)
            fn(*args)
            print "End: %s\n" %(fn.__name__)
        return wrapper
    
    @frame_tests
    def test_fn1():
        print "This is only a test!"
    
    @frame_tests
    def test_fn2(s1):
        print "This is only a test! %s" %(s1)
    
    @frame_tests
    def test_fn3(s1, s2):
        print "This is only a test! %s %s" %(s1, s2)
    
    if __name__ == "__main__":
        test_fn1()
        test_fn2('OK!')
        test_fn3('OK!', 'Just a test!')
    

    结果:

    Start: test_fn1  
    This is only a test!  
    End: test_fn1  
    
    
    Start: test_fn2  
    This is only a test! OK!  
    End: test_fn2  
    
    
    Start: test_fn3  
    This is only a test! OK! Just a test!  
    End: test_fn3
    
  • 15

    当然,您也可以从装饰器函数返回lambdas:

    def makebold(f): 
        return lambda: "<b>" + f() + "</b>"
    def makeitalic(f): 
        return lambda: "<i>" + f() + "</i>"
    
    @makebold
    @makeitalic
    def say():
        return "Hello"
    
    print say()
    
  • 103
    #decorator.py
    def makeHtmlTag(tag, *args, **kwds):
        def real_decorator(fn):
            css_class = " class='{0}'".format(kwds["css_class"]) \
                                     if "css_class" in kwds else ""
            def wrapped(*args, **kwds):
                return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
            return wrapped
        # return decorator dont call it
        return real_decorator
    
    @makeHtmlTag(tag="b", css_class="bold_css")
    @makeHtmlTag(tag="i", css_class="italic_css")
    def hello():
        return "hello world"
    
    print hello()
    

    你也可以在Class中编写装饰器

    #class.py
    class makeHtmlTagClass(object):
        def __init__(self, tag, css_class=""):
            self._tag = tag
            self._css_class = " class='{0}'".format(css_class) \
                                           if css_class != "" else ""
    
        def __call__(self, fn):
            def wrapped(*args, **kwargs):
                return "<" + self._tag + self._css_class+">"  \
                           + fn(*args, **kwargs) + "</" + self._tag + ">"
            return wrapped
    
    @makeHtmlTagClass(tag="b", css_class="bold_css")
    @makeHtmlTagClass(tag="i", css_class="italic_css")
    def hello(name):
        return "Hello, {}".format(name)
    
    print hello("Your name")
    
  • 56

    这个答案早已得到了回答,但我想我会分享我的Decorator类,这使得编写新装饰器变得简单而紧凑 .

    from abc import ABCMeta, abstractclassmethod
    
    class Decorator(metaclass=ABCMeta):
        """ Acts as a base class for all decorators """
    
        def __init__(self):
            self.method = None
    
        def __call__(self, method):
            self.method = method
            return self.call
    
        @abstractclassmethod
        def call(self, *args, **kwargs):
            return self.method(*args, **kwargs)
    

    我认为这使得装饰器的行为非常清晰,但它也使得简单地定义新的装饰器变得容易 . 对于上面列出的示例,您可以将其解决为:

    class MakeBold(Decorator):
        def call():
            return "<b>" + self.method() + "</b>"
    
    class MakeItalic(Decorator):
        def call():
            return "<i>" + self.method() + "</i>"
    
    @MakeBold()
    @MakeItalic()
    def say():
       return "Hello"
    

    您还可以使用它来执行更复杂的任务,例如一个装饰器,它会自动使函数以递归方式应用于迭代器中的所有参数:

    class ApplyRecursive(Decorator):
        def __init__(self, *types):
            super().__init__()
            if not len(types):
                types = (dict, list, tuple, set)
            self._types = types
    
        def call(self, arg):
            if dict in self._types and isinstance(arg, dict):
                return {key: self.call(value) for key, value in arg.items()}
    
            if set in self._types and isinstance(arg, set):
                return set(self.call(value) for value in arg)
    
            if tuple in self._types and isinstance(arg, tuple):
                return tuple(self.call(value) for value in arg)
    
            if list in self._types and isinstance(arg, list):
                return list(self.call(value) for value in arg)
    
            return self.method(arg)
    
    
    @ApplyRecursive(tuple, set, dict)
    def double(arg):
        return 2*arg
    
    print(double(1))
    print(double({'a': 1, 'b': 2}))
    print(double({1, 2, 3}))
    print(double((1, 2, 3, 4)))
    print(double([1, 2, 3, 4, 5]))
    

    哪个印刷品:

    2
    {'a': 2, 'b': 4}
    {2, 4, 6}
    (2, 4, 6, 8)
    [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
    

    请注意,此示例在装饰器的实例化中不包含 list 类型,因此在最终的print语句中,该方法将应用于列表本身,而不是列表的元素 .

  • 3

    以更简单的方式解释装饰器:

    附:

    @decor1
    @decor2
    def func(*args, **kwargs):
        pass
    

    什么时候:

    func(*args, **kwargs)
    

    你真的这样做:

    decor1(decor2(func))(*args, **kwargs)
    

相关问题