目录

Python装饰器

一句话定义

装饰器本质上是一个Python函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。

Python闭包的笔记中提到装饰器的形式:decorated = outer(foo),类似“套娃”。

函数装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def decorator(fn):
    def wrapper(arg):
        """This is docstring of wrapper."""
        print("In wrapper, arg is %s, fn is %s"%(arg, fn.__name__))
        return fn(arg)
    return wrapper

@decorator
def outer(arg):
    """This is docstring of outer."""
    print("In outer, arg is %s"%arg)

print("Finished decorating outer()")
print(outer)
print(outer.__name__)
print(outer.__doc__)
outer("walle")

#output
Finished decorating outer()
<function decorator.<locals>.wrapper at 0x7f60d4af2e60>
wrapper
This is docstring of wrapper.
In wrapper, arg is walle, fn is outer
In outer, arg is walle

print(outer)的输出是wrapper函数可以看出,decorator 这个函数套在outer外面,即原来的outer函数被装饰了,以上的封装过程是:

1
outer = decorator(outer)  # 这里的装饰器返回的是一个函数

outer("walle")等价于decorator(outer)("waller")等价于wrapper("waller")。 所以walle in wrapper, fn is outerwrapperprint()输出的。walle in outer是在wrapperfn(arg)输出的。

由此可以看出,装饰器的价值就是在wrapper的函数中搞事情,而且还可以继续调用被装饰的函数。

类装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Decorator(object):
     def __init__(self, fn):
         print("In __init__, fn is %s"%fn.__name__)
         self.fn = fn
     def __call__(self, arg):
         print("In __call__, arg %s"%arg)
         self.fn(arg)
 
@Decorator
def outer(arg):
    print("In outer, arg %s"%arg)
    
print("Finished decorating outer()")
print(outer)
outer("walle")

#output
In __init__, fn is outer
Finished decorating outer()
<__main__.Decorator object at 0x7f6d773d31f0>
In __call__, arg is walle
In outer, arg is walle

由此执行结果可见,装饰过程只调用了该类的__init__函数,被装饰后实际返回的是一个Decorator的一个实例,以上的封装过程是:

1
outer = Decorator(outer) # 这里的装饰器返回的是一个类的实例对象

因为装饰的过程是类的实例化过程,所以称之为类装饰。

__call__()是一个非常特殊的实例方法。该方法的功能类似于在类中重载()运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

所以outer("walle")就等价于Decorator(outer)("walle")Decorator(outer)是一个实例化对象(这里我们用outer_obj表示),所以最后就是outer_obj('walle'),这就调用到了__call__()的逻辑。

对象装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Decorator(object):
     def __init__(self, arg):
         print("In __init__, arg is %s"%arg)
         self.arg = arg
     def __call__(self, fn):
         print("In __call__, fn is %s"%fn.__name__)
         def wrapper(arg):
            """This is docstring of wrapper."""
            arg = self.arg + ' ' +arg
            fn(arg)
         return wrapper
 
@Decorator("Hello")
def outer(arg1):
    """This is docstring of outer."""
    print("%s In outer"%arg1)
    
print("Finished decorating outer()")
print(outer)
print(outer.__name__)
print(outer.__doc__)
outer("walle")

#output
In __init__, arg is Hello
In __call__, fn is outer
Finished decorating outer()
<function Decorator.__call__.<locals>.wrapper at 0x7f152b182f80>
wrapper
This is docstring of wrapper.
Hello walle In outer

由此执行结果可见,装饰过程只调用了该类的__init__函数和实例函数__call__,装饰后的结果就是执行了__call__的结果,即返回了一个函数,以上的封装过程是:

1
outer = Decorator('hello')(outer) # 这里的装饰器返回的是一个函数

因为装饰的过程是类实例化的对象被调用的过程,所以称之为对象装饰器。

所以outer("walle")就等价于Decorator("Hello")(outer)("walle")Decorator("Hello")是一个实例化对象,Decorator("Hello")(outer)是实例化对象调用了__call__(),该过程返回了wrapper函数。

装饰器顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def decorator_a(func):
    print('In decorator_a')
    def inner_a(*args, **kwargs):
        print('In inner_a')
        func(*args, **kwargs)
        print('In inner_a, post')
    return inner_a

def decorator_b(func):
    print('In decorator_b')
    def inner_b(*args, **kwargs):
        print('In inner_b')
        func(*args, **kwargs)
        print('In inner_b, post')
    return inner_b

@decorator_b
@decorator_a
def outer(arg):
    print("In outer, arg is %s"%arg)

print("Finished decorating outer()")
print(outer)
outer("walle")

#output
In decorator_a
In decorator_b
Finished decorating outer()
<function decorator_b.<locals>.inner_b at 0x7f6b711d6170>
In inner_b
In inner_a
In outer, arg is walle
In inner_a, post
In inner_b, pos

由执行结果中先执行了decorator_a的逻辑,再执行decorator_b的逻辑,最终返回的是inner_b,可见以上的封装过程为:

1
outer = decorator_b(decorator_a(outer)) # 这里的装饰器返回的是一个函数

decorator_a(outer)的执行结果是返回了inner_adecorator_b(inner_a)的执行结果是返回一个inner_b

outer("walle")传入参数进行调用时,就是调用inner_b("walle"),它会先打印In inner_b,然后在inner_b内部调用了funinner_a,所以会再打印In inner_a, 然后再inner_a内部调用的原来的funouter

再形象一点,可以把装饰器想象成洋葱,由近及远对函数进行层层包裹,执行的时候就是拿一把刀从一侧开始切,直到切到另一侧结束。

functools.wraps 属性拷贝

经过装饰器之后的函数还是原来的函数吗?原来的函数肯定还存在的,只不过真正调用的是装饰后生成的新函数。

可以用functools.wraps内部通过partialupdate_wrapper对函数进行再加工,将原始被装饰函数outer的属性拷贝给装饰器函数wrapper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from functools import wraps

class Decorator(object):
     def __init__(self, arg):
         print("In __init__, arg is %s"%arg)
         self.arg = arg
     def __call__(self, fn):
         print("In __call__, fn is %s"%fn.__name__)
         @wraps(fn)
         def wrapper(arg):
            """This is docstring of wrapper."""
            arg = self.arg + ' ' +arg
            fn(arg)
         return wrapper
 
@Decorator("Hello")
def outer(arg1):
    """This is docstring of outer."""
    print("%s In outer"%arg1)
    
print("Finished decorating outer()")
print(outer)
print(outer.__name__)
print(outer.__doc__)
outer("walle")

#output
In __init__, arg is Hello
In __call__, fn is outer
Finished decorating outer()
<function outer at 0x7f3f41656f80>
outer
This is docstring of outer.
Hello walle In outer

可以看出,被装饰后的outer也拥有了原来函数的属性。

关于functools.wraps的实现逻辑,可以参阅Python functools.wraps 深入理解