egon老师知乎文章:
https://zhuanlan.zhihu.com/p/109078881
一 储备知识
1. *args, **kwargs
def index(x, y): ... def wrapper(*args, **kwargs): index(*args, **kwargs) wrapper(111, y = 222) ''' 说明: 为wrapper传的参数会原封不动地转嫁给index, 所以在给wrapper传参时要参照index的参数格式 '''
2. 名称空间与作用域
名称空间的'嵌套'关系是在函数定义阶段, 即检测语法的时候确定的.
3. 函数对象
可以把函数当做另外一个函数的参数传入
可以把函数当做返回值返回
4. 函数的嵌套
嵌套定义
嵌套调用
5. 闭包函数
二 装饰器
1. 什么是装饰器
'器'指的是工具, 可以定义成函数
'装饰器'指的是定义一个函数, 该函数用来为其他函数增加额外的功能.
2. 为何要用装饰器
开放封闭原则
开放: 指的是对拓展功能(在原代码不懂的基础上为其增加功能)是开放的
封闭: 指的是对修改原代码是封闭的
3. 如何使用装饰器
如果原代码本身没有问题, 而需要为其增加新的功能, 则需要使用装饰器.
在不修改被装饰对象原代码以及调用方式的前提下为被装饰对象添加新功能.
三 装饰器实现思路
# 需求: 在不修改index函数原代码以及调用方式的前提下为其添加统计运行时间的功能 # 原代码: import time def index(x, y): time.sleep(1) print('index %s %s'%(x,y)) index(111, 222)
# 方案一:修改了原代码, 没有修改调用方式, 失败 import time def index(x, y): start = time.time() time.sleep(1) print('index %s %s'%(x,y)) stop = time.time() print(stop-start) index(111, 222)
# 方案二:没有修改原代码, 修改了调用方式, 失败 import time def index(x, y): time.sleep(1) print('index %s %s'%(x,y)) def wrapper(): start = time.time() index(111, 222) stop = time.time() print(stop - start)
# 方案三: 没有修改原代码, 修改了调用方式 import time def index(x, y): time.sleep(1) print('index %s %s'%(x,y)) def outter(func): def wrapper(*args, **kwargs): start = time.time() func(*args, **kwargs) stop = time.time() print(stop - start) return wrapper index = outter(index) index(111,222)
# 方案四: 如果被装饰的函数有返回值 import time def index(x, y): time.sleep(1) print('index %s %s'%(x,y)) return 111 def outter(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print(stop - start) return res return wrapper index = outter(index) index(111,222)
# 方案五: 语法优化 # 语法糖: 在被装饰对象正上方的单独一行写@装饰器名字 import time def outter(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print(stop - start) return res return wrapper # index = outter(index) @outter # 运行到此处时, 不会运行下面的函数, 会调用装饰器, 并将下面的函数作为参数传入. def index(x, y): time.sleep(1) print('index %s %s'%(x,y)) return 111 index(111,222)
# 总结 def outter(func): def wrapper(*args, **kwargs): #新增功能代码 res = func(*args, **kwargs) # 新增功能代码 return res return wrapper @outter def index(): print('from index')
偷梁换柱: 将原函数名指向的内存地址偷梁换柱成了wrapper地址. 所以应该将wrapper做的和原函数一样. (参数, 返回值, 属性, 调用方式)
def outter(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper @outter def index(x,y): print(x,y) print(index.__name__) # 结果: wrapper, 而不是index # 将原函数的属性赋值给wrapper函数,包含__name__, __doc__等 from functools import wraps def outter(func): @wraps(func) def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper @outter def index(x,y): print(x,y) print(index.__name__) # 结果:index
四 有参装饰器
''' 由于语法糖@的限制, outter只能有一个参数, 并且该参数只能用来接收被装饰函数的内存地址. 要进行传参, 还需要在外面套一层. 有了三层函数后, 如果还需要传入其他参数, 则无需再在外面套函数, 因为最外面的一层(第三层)不受语法糖的约束, 可以继续传入其他参数. ''' from functools import wraps def auth(db_type): def deco(func): @wraps(func) def wrapper(*args, **kwargs): if db_type == 'file': print('基于文件验证') res = func(*args, **kwargs) return res elif db_type == 'mysql': print('基于mysqk验证') res = func(*args, **kwargs) return res elif db_type == 'sf': print('基于sf验证') res = func(*args, **kwargs) return res else: print('不支持该db_type') return wrapper return deco @auth(db_type='my') # auth(db_type='mysql')运行后返回deco, 则为@deco, 然后执行index = deco(index) def index(x,y): print(x,y) index(111,2222) # 有参装饰器模板 from functools import wraps def 有参装饰器(x, y, z): def outter(func): @ wraps(func) def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper return outter @有参装饰器(参数1, 参数2, 参数3) def 被装饰对象(a, b): pass