首页 > 代码库 > 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()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
老大把工作交给 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()
过了一周 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‘
老大看了下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 ‘‘‘
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 ‘‘‘
装饰器应用实例:
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 装饰器的形成过程