第7章 函数装饰器和闭包
函数装饰器用于“标记”函数,以某种方式增强函数的行为。理解装饰前,必须先理解闭包
本章思维导图:
7.1Python如何计算装饰器句法
@decorate
def target():
print("running target")
等价于
def target():
print("running target")
target=decorate(target)
#最终装饰器函数返回的不一定原来的target函数
def deco(func):
def inner():
print("running inner")
return inner
@deco
def target():
print("running target()")
target()
#审查对象,发现target现在是inner的引用。
装饰器的第一特性:
能把被装饰的函数替换成其他函数
第二个特性:
装饰器在加载模块时立即执行
7.2 Python何时执行装饰器
def register(func):
print("runing register(%s)"%func)
registry.append(func)
return func
@register
def f1():
print("runing f1()")
@register
def f2():
print("runing f2()")
def f3():
print("runing f3()")
def main():
print("runing main()")
print("registry->",registry)
f1()
f2()
f3()
if __name__ == '__main__':
main()
示例7-2主要想强调:
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行,其实在实际情况中:装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
7.3 使用装饰器改进“策略”模式
第六6章中,要计算最佳折扣,需要维护一个函数列表,promos=[]
如果忘记加入新的折扣策略,就会造成难以发现的错误。
用装饰器解决该问题:
promos=[]
def promotion(promo_func):
promos.append(promo_func)
return promo_func
@promotion
def method1():
print("我是策略一")
@promotion
def method2():
print("我是策略二")
这个方法的优点:
促销策略函数无需使用特殊的名称
@promotion
装饰器突出了被装饰的函数的作用
还便于临时禁用某个促销策略:只需把装饰器注释掉。
促销折扣策略可以在其他模块中定义,
在系统中的任何地方都行,只要使用
装饰器即可
这个例子返回的还是传入的参数,但是通常装饰器会返回一个内部函数。
使用内部函数(嵌套函数),就要深刻的理解闭包操作
运行该代码:
b=6
def f(a):
print(a)
print(b)
b=9
#赋值了,此时认为b是局部变量
查看结果:
因为:
Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量
#正确运行:
b=6
def f(a):
global b
#特别声明b是全局变量
print(a)
print(b)
b=9
7.5 闭包
闭包:函数的变量作用域进行了拓展
拓展后的作用域:
包含了在函数体种引用,但是没在函数体中定义的,非全局变量(被称为自由变量)
理解一个计算收盘平均价格的函数:
def make_averager():
series=[]
#是自由变量
#保存收盘价格列表
def averager(new_value):
series.append(new_value)
total=sum(series)
return total/len(series)
return averager
avg=make_averager()
问题是:
调用avg(10)
时
make_averager
函数已经返回了,而它的本地作用域应该不存在了。
运行如下代码:
avg.__code__.co_varnames
avg.__code__.co_freevars
output:
闭包的变量作用域如下:
在总结一下
看下函数glabol和nonlocal的区别
global
声明全局变量,
nonlocal
声明嵌套函数中的自由变量
def make_averager():
count=0
total=0
def averager(new_value):
count+=1
total+=new_value
#注意报错
return total.count
return averager
这里,count,total都是不可变变量,此时如果运行,
count会被认为是局部变量,所以必须用nonlocal来声明自由变量
def make_averager():
count=0
total=0
def averager(new_value):
nonlocal count,total
count+=1
total+=new_value
#注意报错
return total.count
return averager
7.7 实现一个简单的装饰器
# 装饰器用来计算一个函数的运行时间
import time
def clock(func):
def clocked(*args):
#接受任意个定位参数
t0=time.perf_counter()
result=func(*args)
#func是自由变量
t1=time.perf_counter()-t0
#程序运行使用时间
name=func.__name__
arg_str=",".join(repr(arg) for arg in args)
print('[%0.8fs]%s(%s)->%r'%(t1,name,arg_str, result))
#%0.8f小数点后显示8位的s
return result
return clocked
import time
from test import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n <2 else n*factorial(n-1)
if __name__ == '__main__':
snooze(.123)
factorial(6)
这种装饰器有以下缺点:
不支持关键字传参
覆盖了被装饰函数的__name__/__doc__属性
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args,**kwargs):
#接受任意个定位参数
t0=time.perf_counter()
result=func(*args,**kwargs)
#func是自由变量
t1=time.perf_counter()-t0
#程序运行使用时间
name=func.__name__
arg_lst=[]
if args:
arg_lst.append(",".join(repr(arg) for arg in args))
if kwargs:
pairs=['%s=%r'%(k,w)for k,w in sorted(kwargs.items())]
#关键字传参保存在字典里。
arg_lst.append(",".join(pairs))
arg_str=','.join(arg_lst)
print('[%0.8fs]%s->%r'%(t1,name,arg_str,result))
#%0.8f小数点后显示8位的s
return result
return clocked
在总结两个拿来即用的装饰器:
lru_cache 和 singledispatch
7.8 标准库中的装饰器
property、classmethod和staticmethod
先不讨论这三个装饰方法的函数
另一个常见的装饰器是functools.wraps,
它的作用是协助构建行为良好的装饰器
:支持关键字传参数
:不覆盖被装饰函数的属性
7.8.1 使用functools.lru_cache做备忘
这是一个优化技术,
把耗时的函数结果保存起来,避免传入相同参数的时候重复计算
所以可以用来优化递归算法
从Web中获取信息的应用中也能发挥巨大作用。
import functools
from test import clock
@functools.lru_cache()
@clock
#装饰器叠用,真尼玛牛皮
def fibonacci(n):
if n<2:
return(n)
return fibonacci(n-2)+fibonacci(n-1)
if __name__ == '__main__':
print(fibonacci(10))
多次计算相同参数的值,浪费时间
使用缓存,速度更快
如果一个算法,用了递归,可以用这种方法优化
7.8.2 单分派泛函数
‘’’’’’'未完待续‘’‘’‘’‘
7.9叠放装饰器
@d1
@d2
def f():
print("f")
等价于:
def f():
print('f')
f=d1(d2(f))
7.10 参数化装饰器
‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’'未完待续~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~