装饰器的完整实现及原理

Posted reklen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器的完整实现及原理相关的知识,希望对你有一定的参考价值。

1、简单装饰器

说明:代码在下边。装饰前后,我们都打印一遍如下内容,做一下对比。

  • print(foo)           # 打印当前函数对象
  • print(foo.__name__)  # 打印foo函数的函数名
  • print(foo.__doc__)   # 打印foo函数的文档字符串(docString)

装饰之前:

  • <function foo at 0x000002569AAB5620>
  • foo
  • this is foo

装饰之后:

  • <function check_result.<locals>.wrapper at 0x00000250E11F56A8>
  • wrapper
  • this is wrapper

可以看出,foo的引用发生了变化。装饰的过程是这样的,python解释器会将foo作为参数传递给check_result函数,func就引用了foo函数,并将返回值给了foo,因为返回值wrapper引用了wrapper函数,所以foo的引用就变为了wrapper函数。这时再去执行foo,就等于是在执行wrapper函数了。需要注意的是在装饰之后,我我们的foo函数并没有销毁,而是被func引用着。不然执行的时候也不会有结果了。可是怎么查看呢?使用 print(foo.__closure__[0].cell_contents) 则会看到输出 <function foo at 0x000001F2C7465620> 。这样我就找到了最开始定义的foo函数了!

 1 ‘‘‘
 2 简单装饰器:
 3     实现装饰后函数的功能是如果x和y相乘为负数,则返回0
 4 ‘‘‘
 5 
 6 def check_result(func):
 7     ‘‘‘hahah‘‘‘
 8     def wrapper(*args, **kwargs):
 9         ‘‘‘this is wrapper‘‘‘
10         result = func(*args, **kwargs)
11 
12         if result < 0:
13             return 0
14         else:
15             return result
16     return wrapper
17 
18 
19 @check_result
20 def foo(x, y):
21     ‘‘‘this is foo‘‘‘
22     return x * y
23 
24 
25 
26 
27 # 装饰过程拆解
28 # wrapper = check_result(foo)
29 # foo = wrapper

2、多层装饰器

说明:在理解简单装饰器的基础上,再来看多层的装饰器,会很好理解。只需要关注一下多层装饰器的装饰顺序和执行顺序即可。

 1 ‘‘‘
 2 多层装饰器
 3 ‘‘‘
 4 
 5 def deco1(func):
 6     def wrapper1(*args, **kwargs):
 7         ‘‘‘this is wrapper1‘‘‘
 8         print(start 1)
 9         result = func(*args, **kwargs)
10         print(end   1)
11         return result
12     return wrapper1
13 
14 
15 def deco2(func):
16     def wrapper2(*args, **kwargs):
17         ‘‘‘this is wrapper2‘‘‘
18         print(start 2)
19         result = func(*args, **kwargs)
20         print(end   2)
21         return result
22     return wrapper2
23 
24 
25 def deco3(func):
26     def wrapper3(*args, **kwargs):
27         ‘‘‘this is wrapper3‘‘‘
28         print(start 3)
29         result = func(*args, **kwargs)
30         print(end   3)
31         return result
32     return wrapper3
33 
34 
35 @deco1
36 @deco2
37 @deco3
38 def foo(x, y):
39     ‘‘‘this is foo‘‘‘
40     return x * y
41 
42 print(foo(8, 9))
43 ‘‘‘
44 输出结果:
45     start 1
46     start 2
47     start 3
48     end   3
49     end   2
50     end   1
51     72
52 ‘‘‘
53 
54 
55 ‘‘‘
56 装饰的过程:
57     wrapper3 = deco3(foo)
58     wrapper2 = deco2(wrapper3)
59     wrapper1 = deco1(wrapper2)
60     foo = wrapper1
61 
62 
63 执行的过程:正好和装饰的过程相反。
64         foo(8, 9)--->wrapper1(8, 9)--->deco1(wrapper2)(8, 9)--->
65                                                                 |
66                                                                 v
67         deco1( deco2( deco3(foo) ) )(8, 9)<---deco1( deco2(wrapper3) )(8, 9)
68         类比穿衣服,穿(装饰)的时候从里往外一层套一层,脱(执行)的时候从外到里一层一层脱。
69 ‘‘‘

3、带参装饰器

说明:传入函数执行的次数,统计执行完成的时间。

实现方法:使用工厂模式,定义一个工厂函数,它本身不是装饰器,但是返回值是一个装饰器,是用来"生产"装饰器的。如下所示:

 1 ‘‘‘
 2 带参装饰器
 3 ‘‘‘
 4 
 5 import time
 6 
 7 def timer(count):
 8     def deco(func):
 9         def wrapper(*args, **kwargs):
10             ‘‘‘this is wrapper‘‘‘
11             t1 = time.time()
12             for i in range(count):
13                 result = func(*args, **kwargs)
14             t2 = time.time()
15             print(t2 - t1)
16             return result
17         return wrapper
18     return deco
19 
20 
21 @timer(10000000)      # 获取foo执行10000000次的时间
22 def foo(x, y):
23     ‘‘‘this is foo‘‘‘
24     return x * y

4、类装饰器

说明:想要明白类装饰器的原理,我们先要了解一下__call__这个方法。这个方法是python中所有能被调用的对象具有的内置方法,比如类,函数。调用类的过程就是得到一个类的实例的过程,如果要做类装饰器我们得让该类的实例可以被调用。装饰器的本质也是一个函数被调用执行的过程。先看下面一段代码:

 1 class A:
 2     pass
 3 
 4 
 5 def foo(x, y):
 6     return x, y
 7 
 8 
 9 print(A.__call__)
10 print(foo.__call__)
11 
12 print(foo.__call__(3, 5))   # 打印结果  15
13 print(foo(3, 5))            # 打印结果  15

打印结果为:可以看到,有__call__方法的才能被调用。还有一个方法判断一个对象是否能被调用 callable() ,传入对象,若返回True则表示能被调用,否则不能被调用。

 <method-wrapper __call__ of type object at 0x000002CE9522BF48>

<method-wrapper ‘__call__‘ of function object at 0x000002CE953B1E18>

根据这个特性,我们设计我们的类装饰器:

 1 ‘‘‘
 2 类装饰器
 3 ‘‘‘
 4 
 5 class Deco:
 6     ‘‘‘this is Deco‘‘‘
 7     def __init__(self, func):
 8         self.func = func
 9 
10     def __call__(self, *args, **kwargs):
11         result = self.func(*args, **kwargs)
12         if result < 0:
13             return 0
14         else:
15             return result
16 
17 
18 @Deco                 # 装饰后若结果为负数则返回0
19 def foo(x, y):
20     ‘‘‘this is foo‘‘‘
21     return x * y
22 
23 ‘‘‘
24 装饰过程如下:
25     这个时候foo引用了Deco的一个实例,执行foo()也就是实例调用__call__方法的过程。
26 ‘‘‘
27 foo = Deco(foo)

 

以上是关于装饰器的完整实现及原理的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript 装饰器的执行原理

Python装饰器的实现原理

python闭包及装饰器

装饰器笔记

Python @函数装饰器及用法

python装饰器