函数装饰器和闭包
装饰器的作用
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。
装饰器是可调用对象,参数是另一个函数(被装饰的函数)。
装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
装饰器的特性:(1)把被装饰的函数替换成其他函数。(2)装饰器在加载模块时立即执行
变量作用域规则:Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
闭包:延伸了作用域的函数。包含函数定义体中引用,同时还可以访问定义体之外定义的非全局变量。
def make_average(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return average
>>> avg = make_average()
>>> avg(10)
在averager函数中,series是自由变量(free variable)指未在本地作用域中绑定的变量。
Python在 __code__ 属性中保存 局部变量和自由变量的名称。
>>> avg.__code__.co_varname ('new_value','total') >>> avg.__code__.co_freevars ('series',)
series的绑定在返回的avg对象的__closure__属性中。
avg.__closure__中的各个元素对应于avg.__code__.co_freevars中的一个名称。这些元素是cell对象。
它们的值保存在cell_contents属性中。
>>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x000001C2EBE261C8: list object at 0x000001C2EBE33988>,) >>> avg.__closure__[0].cell_contents [10,]
nonlocal声明:把变量标记为自由变量。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total/count return average
当变量是数字或任何不可变类型时,averager的定义体中为count赋值,实质会隐式创建局部变量count。
引入nonlocal声明,把变量标记为自由变量,即使在函数中为变量赋予新值,也会变成自由变量。
标准库中的装饰器
functools.lru_cache 实现了备忘功能。把耗时的函数结果保存起来,避免传入相同的参数时重复计算。
递归函数适合使用lru_cache。
functools.lru_cache(maxsize=128,typed=Fales)
maxsize参数指定存储多少个调用的结果。缓存满了后,旧得结果会被扔掉。maxsize应该设置为2的幂
typed参数为True,不同参数类型得到的结果分开保存。
▲ lru_cache使用字典存储结果,键根据调用时传入的定位参数和关键字参数创建,所以被装饰的函数所有参数必须是可散列的。
functools.singledispatch 装饰器可以把整体方案拆分成多个模块。
根据第一个参数的类型,以不同的方式执行相同操作的一组函数。
from functools import singledispatch from collections import abc import numbers @singledispatch def inp_check(obj): print('Main inp_check') @inp_check.register(str) def _(text): print('Text %s'%text) @inp_check.register(numbers.Integral) def _(n): print('Int %s' %n) @inp_check.register(tuple) @inp_check.register(abc.MutableSequence) def _(seq): print('List %s'%seq)
注册的专门函数应该处理抽象基类(numbers.Integral和abc.MutableSequence),不需处理具体实现(int、list)。
这样,代码支持的兼容类型更广泛。
参数化装饰器
Python把被装饰的函数作为第一个参数传递给装饰器函数,那么如何给装饰器传递其他参数呢?
def outer(func): def inner(*args,**kwargs): print('Do Something..') result = func(*args,**kwargs) return result return inner @outer def f1(num): print('Number %s'%num) # ========= 等效于
f1 = outer(f1)
我们可以创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它运用到要装饰的函数上。
def register(active=False): def outer(func): def inner(*args,**kwargs): print('Do Something..') result = func(*args,**kwargs) return result return inner return outer @register(active=True) def f1(num): print('Number %s'%num) # =========等效于 f1 = register(active=True)(f1)