首页 > 代码库 > Python 装饰器的形成过程

Python 装饰器的形成过程

装饰器
  定义:本质是函数,(装饰其他函数),即为其他函数添加附加功能。
  原则: 1、不能修改被装饰的函数的源代码;
            2、不能修改被装饰的函数的调用方式。

实现装饰器知识储备:
   1. 函数即‘变量‘        
   2. 高阶函数
       a. 把一个函数名当作实参传递给另一个函数(在不修改被装饰函数源代码的前提下为其添加新功能)
       b. 返回值中包含函数名(不修改函数的调用方式)
   3. 嵌套函数

高阶函数 + 嵌套函数 (组成)--> 装饰器

  1、必备                                                

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: antcolonies


‘‘‘第一波‘‘‘

def foo():
    print(‘in the foo‘)

foo               # 函数名,相当于变量
print(foo)        # <function foo at 0x0000000001CFCBF8>  变量的索引地址
foo()             # in the foo    执行/调用函数foo


‘‘‘第二波‘‘‘

def foo(x):
    print(‘in the foo_%d‘ %x)

foo = lambda x:x+1

print(foo)

print(foo(1))     # 执行下面的lambda表达式,而不再是原来的foo函数,因为函数 foo 被重新定义了


‘‘‘
<function foo at 0x0000000001CFCBF8>
in the foo
<function <lambda> at 0x0000000001CFCBF8>
2
‘‘‘

 

  2、需求来了                                       

初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 ############### 基础平台提供的功能如下 ###############
 6 
 7 def f1():
 8     print(in the f1)
 9 
10 def f2():
11     print(in the f2)
12 
13 def f3():
14     print(in the f3)
15 
16 def f4():
17     print(in the f4)
18 
19 ############### 业务部门A 调用基础平台提供的功能 ###############
20 
21 f1()
22 f2()
23 f3()
24 f4()
25 
26 ############### 业务部门B 调用基础平台提供的功能 ###############
27 
28 f1()
29 f2()
30 f3()
31 f4()
function call

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给 Low B,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。

 当天Low B 被开除了...

老大把工作交给 Low BB,他是这么做的:

只对基础平台的代码进行重构,让N业务部门无需做任何修改。
技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 ############### 基础平台提供的功能如下 ############### 
 6 
 7 def f1():
 8     # 验证1
 9     # 验证2
10     # 验证3
11     print(in the f1)
12 
13 def f2():
14     # 验证1
15     # 验证2
16     # 验证3
17     print(in the f2)
18 
19 def f3():
20     # 验证1
21     # 验证2
22     # 验证3
23     print(in the f3)
24 
25 def f4():
26     # 验证1
27     # 验证2
28     # 验证3
29     print(in the f4)
30 
31 ############### 业务部门不变 ############### 
32 ### 业务部门A 调用基础平台提供的功能### 
33 
34 f1()
35 f2()
36 f3()
37 f4()
38 
39 ### 业务部门B 调用基础平台提供的功能 ### 
40 
41 f1()
42 f2()
43 f3()
44 f4()
authoration

过了一周 Low BB 被开除了...

老大把工作交给 Low BBB,他是这么做的:

只对基础平台的代码进行重构,其他业务部门无需做任何修改
技术分享
 1 ############### 基础平台提供的功能如下 ############### 
 2 
 3 def check_login():
 4     # 验证1
 5     # 验证2
 6     # 验证3
 7     pass
 8 
 9 
10 def f1():    
11     check_login()
12     print f1
13 
14 def f2():    
15     check_login()
16     print f2
17 
18 def f3():    
19     check_login()
20     print f3
21 
22 def f4():    
23     check_login()    
24     print f4
authoration_1

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:

老大说:

写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:对扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: antcolonies

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        return func()
    return inner

@w1
def f1():
    print(‘in the f1‘)

@w1
def f2():
    print(‘in the f2‘)

@w1
def f3():
    print(‘in the f3‘)

@w1
def f4():
    print(‘in the f4‘)

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?

老大正要生气,突然Low BBB的手机掉到地上,恰恰屏保就是Low BBB的女友照片,老大一看一紧一抖,喜笑颜开,交定了Low BBB这个朋友。详细的开始讲解了:

以下面为例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: antcolonies

import time

def timer(func):
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print(‘the func run time is %s‘ %(stop_time - start_time))
    return deco

@timer        # test1 = timer(test1)
def test1():
    time.sleep(3)
    print(‘in the test1‘)

test1()

下面解析一下以上代码的执行(解释器解释)过程:

1. 解释 import time

2. 初始化 def timer(func):  ,即将函数变量timer和形参入栈内存

3. 解释 第5行和第6行代码,因为@timer;def test1(): 是一个整体结构(语法糖),相当于第2步的初始化,解释的结果是: test1 = timer(test1)

4. 执行test1 = timer(test1),注意此时timer(test1)中的test1是def test1(): 中的函数名,存储于栈内存,timer(test1)将函数名test1作为实参传入timer()函数

5. 接着初始化def deco(): 入栈内存,并将函数名deco以返回值的形式赋值给第3步中的test1 = timer(test1),故此时test1的值为函数deco的函数体的内存地址

6. 接着至第20行解析test1(),由于此时test1的值为deco的内存地址,故接下来会调用函数deco

7. 依次执行函数deco的函数体start_time = time.time(),func()

8. 由于第3步中有实参传入,故此时的形参func即为test1, 即执行test1(),调用函数def test1(): 并执行之

9. 接着执行第11和12行。整个程序模块执行完毕。

执行结果:

in the test1
the func run time is 9.18652606010437(debug模式下得出的结果)

先把上述流程看懂,之后还会继续更新...

  3、问答时间                                         

问题:当被装饰(或扩充)的函数有参数,而且参数个数不一致时:

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 import time
 6 
 7 def timer(func):
 8     def deco(argv1,argv2):
 9         start_time = time.time()
10         func(argv1,argv2)
11         stop_time = time.time()
12         print(the func run time is %s %(stop_time - start_time))
13     return deco
14 
15 @timer
16 def test2(name, age):
17     time.sleep(2)
18     print(in the test2: %s %d %(name, age))
19 
20 test2(alex,22)
21 
22 ‘‘‘
23 in the test2: alex 22
24 the func run time is 2.000114917755127
25 ‘‘‘
有参数
技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 import time
 6 
 7 def timer(func):
 8     def deco():
 9         start_time = time.time()
10         func()
11         stop_time = time.time()
12         print(the func run time is %s %(stop_time - start_time))
13     return deco
14 
15 @timer        # test1 = timer(test1)
16 def test1():
17     time.sleep(2)
18     print(in the test1)
19 
20 @timer
21 def test2(name, age):
22     time.sleep(2)
23     print(in the test2: %s %d %(name, age))
24 
25 test1()
26 test2(alex,22)
27 
28 ‘‘‘
29 D:\Python\python.exe E:/python14_workspace/s14/day04/decorator_4_2.py
30 in the test1
31 the func run time is 2.0001139640808105
32 Traceback (most recent call last):
33   File "E:/python14_workspace/s14/day04/decorator_4_2.py", line 26, in <module>
34     test2(‘alex‘,22)
35 TypeError: deco() takes 0 positional arguments but 2 were given
36 
37 Process finished with exit code 1
38 ‘‘‘
参数不固定

发现当参数不固定时,会出现错误,故需将其改进:

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 import time
 6 
 7 def timer(func):
 8     def deco(*argv):
 9         start_time = time.time()
10         func(*argv)
11         stop_time = time.time()
12         print(the func run time is %s %(stop_time - start_time))
13     return deco
14 
15 @timer        # test1 = timer(test1)
16 def test1():
17     time.sleep(2)
18     print(in the test1)
19 
20 @timer
21 def test2(name, age):
22     time.sleep(2)
23     print(in the test2: %s %d %(name, age))
24 
25 test1()
26 test2(alex,22)
27 
28 ‘‘‘
29 in the test1
30 the func run time is 2.000114917755127
31 in the test2: alex 22
32 the func run time is 2.0001139640808105
33 ‘‘‘
不固定参数形式_ *args
技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 import time
 6 
 7 def timer(func):
 8     def deco(*args,**kwargs):
 9         start_time = time.time()
10         func(*args,**kwargs)
11         stop_time = time.time()
12         print(the func run time is %s %(stop_time - start_time))
13     return deco
14 
15 @timer        # test1 = timer(test1)
16 def test1():
17     time.sleep(2)
18     print(in the test1)
19 
20 @timer
21 def test2(name, age):
22     time.sleep(2)
23     print(in the test2: %s %d %(name, age))
24 
25 test1()
26 test2(alex,age = 22)   # 有关键参数时
27 
28 ‘‘‘
29 in the test1
30 the func run time is 2.0001139640808105
31 in the test2: alex 22
32 the func run time is 2.000114917755127
33 ‘‘‘
有关键参数形式_ *args,**kwargs

装饰器应用实例:

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 usr,passwd = alex,abc123
 6 
 7 def auth(func):
 8     def wrapper(*args, **kwargs):
 9         username = input("username:").strip()
10         password = input("password:").strip()
11         if username == usr and password == passwd:
12             print(\033[32;1mUser has passed authentication.\033[0m)
13             return func(*args,**kwargs)
14         else:
15             exit(\033[31;1mInvalid username or password.\033[0m)
16     return wrapper
17 
18 
19 def index():
20     print(welcome to index page.)
21 
22 @auth
23 def home():
24     print(welcome to home page.)
25     return from home
26 
27 @auth
28 def bbs():
29     print(welcome to bbs page.)
30 
31 index()
32 print(home())
33 bbs()
论坛

出现以上的当登录home()和bbs模块时,需要通过验证。但是当不同的登录渠道需要不同的验证处理时,以上模块代码就傻眼了。故针对不同的登录渠道需要个性化的验证:

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 usr,passwd = alex,abc123
 6 
 7 def auth(auth_type):                   # 增加个性化定制参数 
 8     print(auth func:, auth_type)
 9     def outer_wrapper(func):         
10         def wrapper(*args, **kwargs):
11             print(auth func args:, *args, **kwargs)
12             if auth_type == local:
13                 username = input("username:").strip()
14                 password = input("password:").strip()
15                 if username == usr and password == passwd:
16                     print(\033[32;1mUser has passed authentication.\033[0m)
17                     return func(*args, **kwargs)
18                 else:
19                     exit(\033[31;1mInvalid username or password.\033[0m)
20             elif auth_type == ldap:
21                 print(get hell out here!)
22         return wrapper
23     return outer_wrapper
24 
25 
26 def index():
27     print(welcome to index page.)
28 
29 @auth(auth_type = local)         # 个性化定制
30 def home():
31     print(welcome to home page.)
32     return from home
33 
34 @auth(auth_type = ldap)
35 def bbs():
36     print(welcome to bbs page.)
37 
38 index()
39 print(home())
40 bbs()
个性化验证

 

  4、functools.wraps                           

上述的装饰器虽然已经完成了其应有的功能,即:装饰器内的函数代指了原函数,注意其只是代指而非相等,原函数的元信息没有被赋值到装饰器函数内部。例如:函数的注释信息。

技术分享
 1 def outer(func):
 2     def inner(*args, **kwargs):
 3         print(inner.__doc__)  # None
 4         return func()
 5     return inner
 6 
 7 @outer
 8 def function():
 9     """
10     asdfasd
11     :return:
12     """
13     print(func)
无元信息

如果使用functools模块中的@functools.wraps装饰的装饰器内的函数,那么就会代指元信息和函数。

技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: antcolonies
 4 
 5 import functools
 6 
 7 def outer(func):
 8     @functools.wraps(func)
 9     def inner(*args, **kwargs):
10         print(inner.__doc__)
11         return func()
12     return inner
13 
14 @outer
15 def function():
16     """
17     decorator
18     asdfasd
19     :return:
20     """
21     print(func)
22 
23 function()
24 
25 ‘‘‘
26 
27     decorator
28     asdfasd
29     :return:
30 
31 func
32 ‘‘‘
含元信息

 

Python 装饰器的形成过程