3.23
上节回顾
*args,**kwargs
-
*args:*用于把实参中多出来的位置实参赋值给args
-
**kwargs,**把多出来的关键字实参变成字典赋值给kwargs
-
实参中 ,* 用于打散
-
def index(*args,**kwargs): print(x,y) def wrapper(*args,**kwargs): # (1,2,34),{'x':1,'y':2} index(*args,**kwargs) # index(1,2,34,x=1,y=2) wrapper(1,2,34,x=1,y=2)
用这种格式,将wrapper调用时的实参格式原封不动传给index
名称空间与作用域
- 名称空间的嵌套关系时在函数定义阶段,即检测语法的时候确定的
- 函数对象
- 可以把函数当作另外一个函数的参数传入
- 函数可以当作返回值
函数的嵌套定义
-
在函数内包一个函数
def outter(): def inner(): pass return inner # 不加括号,返回 inner的内存地址
闭包函数
-
内嵌函数,且引用了其外层函数中的名字
def f1(): x = 1 y = 2 z = 3 f1()
f1的名称空间正常在调用完之后会被清理,但是如果其中的名字被其他地方引用到了,那他的引用计数还存在,他的名称空间不会被回收
传参的方式
-
通过参数的形式为函数传值
-
通过闭包的方式为函数体传值
def outter(x): def wrapper(): print(x) return wrapper # 处于局部 wrapper = outter(1) # 处于全局
-
名称空间:不同的名称空间可以使用同样的名字,所以把局部的函数赋值给全局的同名函数是可以的
正课
装饰器
什么是
-
器指的是工具,可以定义成函数
-
装饰指的是为其他工具添加功能
-
装饰器:
定义一个工具(类,函数,目前只学了函数),用来为其他函数增加功能
为何要用
在真实开发环境中对原功能进行拓展,应遵循开放封闭原则,线上运行的软件不能轻易修改或关闭
开放封闭:想为一个函数增加新功能,但是不想改变这个函数的源代码
- 开放:对拓展功能开放
- 封闭:对修改源代码是封闭的
为原函数增加新功能的时候,可能涉及到很多个地方,如果错了一个,则整体程序报错,牵一发动全身。
装饰器就是在不修改被装饰对象源代码及调用方式为前提下,为原函数增加功能
如何用
示例
def index(x,y):
print('index %s %s'%(x,y))
index(111,222)
这里有一个普通的函数,现在想为这个函数加上一个新功能,统计函数的运行时间
方法一:修改了源代码
-
用time 模块
time.time
返回当前时间戳:从1970年到当前时间的秒数 -
运行函数前time.time,运行结束后再time.time,函数结束时间 减 函数起始时间就是函数执行时间
import time def index(x,y): start = time.time() time.sleep(1) print('index %s %s'%(x,y)) end = time.time() print(end - start) index(111,222)
确实实现了功能,没有修改调用函的方式,但是修改了源代码,不符合开放封闭原则
方法二:调用前后加语句
-
在调用前后用time.time
import time def index(x,y): time.sleep(1) print('index %s %s'%(x,y)) start = time.time() index(111, 222) end = time.time() print(end - start)
在每一个需要统计时间的时候,调用前后都加上这几行代码,实现了不改变源代码,又不改变调用方式,但是在很多个地方都需要这个新功能,都需要重复写这几句语句,代码十分冗余,不妥
解决办法:装饰器
-
使用闭包函数,解决了代码冗余的问题,但是调用方式变成 wrapper(),且index被写死111,222
import time def index(x,y): time.sleep(1) print('index %s %s'%(x,y)) def wrapper(x,y): start = time.time() index(111, 222) end = time.time() print(end - start) wrapper()
-
改进1,不把wrapper写死:
index(x,y) ... wrapper(111,222)
-
改进2,使用 **,可以传入多个参数,把参数写活了:
def index(x,y,z): ... def wrapper(*args,**kwargs): ... index(*args,**kwargs) wrapper(111,222,33,444)
-
改进3:不想只装饰index一个函数,想让wrapper可以装饰所有函数:需要一个函数作为参数传进wrapper,但是不能通过形参把这个函数传进来:使用闭包函数将函数当作参数传递,把函数写活了
def outter(func): # 把要装饰的函数当作参数传给装饰器的包 def wrapper(*args,**kwargs): func(*args,**kwargs) return wrapper def index(x,y): print(x,y) f = outter(index) # 实际上是运行了wrapper,wrapper中的func实际上是调用时当作参数传入的函数 f(1,2)
解决了不改变源代码,写死的问题,但是也改变了调用方式
-
最终:不改变调用方式
用原函数的名字命名改进的函数,偷梁换柱
index = outter(index) index(1,2)
总结方法
-
将装饰的功能做成闭包函数
-
在包中将要装饰的函数当作参数传入嵌套函数
-
闭包函数用包中的函数为对象做装饰,接收原函数的参数
-
包函数返回其内嵌函数的函数体
-
改名,偷梁换柱
index = outter(index) index()
最终要达到的效果:以假乱真
这种方法在原函数有返回值时有问题:原函数已经被替换成函数wrapper,调用index实际上是在调用wrapper,而wrapper没有返回值,所以需要让wrapper返回原函数的返回值
最终改进
在装饰器内,返回原函数本来应该返回的值
def outter(func): # 把要装饰的函数当作参数传给装饰器的包
def wrapper(*args,**kwargs):
func(*args,**kwargs)
res = func(*args,**kwargs)
return res # wrapper函数原函数的运行结果
return wrapper # 包函数在偷梁换柱的时候返回内嵌函数的函数体
def index(x,y):
print(x,y)
index = outter(index)
index(1,2)
最终达到了,调用方式不变,原代码不变,返回值不变,实现了以假乱真
语法糖 @装饰器名字
对每次装饰函数都要偷梁换柱原函数的名字
-
index = outter(index) index(1,2)
对这种有语句简洁的语法:在被装饰对象正上方的单独一行写 @装饰器名字
,python自动给你偷梁换柱
-
def timmer(): # 装饰器必须在被装饰函数的上面 ... @timmer # 相当于帮你做了 index = timmer(index) def index(): ...
-
装饰器需要放到被装饰器的上面,否则运行到@这一行代码,装饰器还没有被定义,则报错
-
一个函数可以叠加多个装饰器
加载顺序:
@deco1 # deco1.wrapper的内存地址 = deco1(index) @deco2 # deco2.wrapper的内存地址 = deco2(index) @deco3 # deco3.wrapper的内存地址 = deco3(index) def index(): ...
总结
基本模板
def outter(func):
def wrapper(*args,**kwargs):
res = func()
# 新功能
...
return res
return wrapper
@outter
def index(x,y):
# 原功能
...
index(x,y)
这就得到一个模板,原功能写到index,新功能写到wrapper,就得到了一个完整的无参装饰器