这一章我们来探讨函数装饰器和闭包
1.python模块在导入时会执行装饰器,对于被装饰器装饰的函数,需要明确调用才会执行。
2.函数可以读取不在本定义域内的变量
b = 6
def f():
a = 6
print(a)
print(b)
但同时有时候是另人吃惊的
b = 6
def f():
a = 6
print(a)
print(b)
b = 7
运行这个示例就会发现,当我们有一个在函数体内给b赋值的语句时,Python会在编译函数体时就认定b是函数内定义的一个变量,而不是一个在外部的变量。有了这个假设,当python遇到print(b)时,它只会在函数体内找值,因为赋值在打印后,所以找不到会报错。
怎么解决?我们需要告诉Python, 我们这个b是一个全局的,你应该在全局环境中去找,而不是在函数体内去找。
所以加句global b
3.闭包
我认为,闭包是一种函数,在这个函数内部还有若干函数,也就是说,只有嵌套函数才存在闭包。
闭包这种函数,它的定义域是延伸了的,也就是说它能找不在本定义体内的非全局变量。注意是非全局的。
接下来我们定义一个能保存并且利用历史值计算平均值的函数
class Avarger:
def __init__(self):
self.data = []
def __call__(self, x):
self.data.append(x)
return sum(self.data)/len(self.data)
但这是通过call, 通过类来实现的,我们怎么通过函数来实现呢?
这就用到了闭包!
def Avarger():
data = []
def avg(x):
data.append(x)
return sum(data)/len(data)
return avg
当我们这样测试时
avg = Avarger()
当Avarget返回时,data已经消失了,那avg怎么找历史值呢?其实那个data作为一个自由变量,已经被绑定在了avg函数上!
这就是闭包,这里不详述怎么找这个绑定。
但是闭包有时会遇到一些问题
比如下面的示例
def A():
count = 0
total = 0
def avg(x):
count += 1
total += x
return total/count
这个代码是会失败的,因为你在使用count += 1时, 也就是count = count + 1,这时python看到这句话它会认为count是一个avg内部的本地变量,而不是绑定在avg上的自由变量。所以我们要告诉Python, nonlocal count, total, 它们不是本地变量,你不要妄想在avg内找了,去外面用闭包机制找。
7.7自己建立一个简单的装饰器
之前我们定义的装饰器都是很简单的,比如那个注册的函数。一般来说在装饰器内部还会有定义的函数,例如:
一个记录作用时间的函数
def clock(func):
def clocked(*args):
......
这里我省略了实现细节,只是说说关键点。我们在这里的装饰器是clock,因为它接受一个func,一旦它接受了这个func,func在clock内部会成为本地变量,同时根据闭包规则它会成为clocked的自由变量。也就是说我们在clocked内部可以直接使用func。
但同时这个clock有个问题
比如我们让f1 = clock(f1), 这是f1.__name__不是f1 ,是clocked。你如果不喜欢,在def clocked前加一句@functools.wraps(func)
7.8标准库中的装饰器
之前其实提过functools.wraps(func),接下来介绍几个更强大的。
如果你想避免某些递归函数的重复计算,比如找第n个斐波那契数。
@clock
def f(n):
if n<2:
return n
return f(n-1) + f(n-2)
这时若我们在@clock上加@functools.lru_cache()
你可以看到我们重复过的计算是不会再做的,可以极大提升递归效率。
另一个不介绍。
7.9叠放装饰器的意义
@d1
@d2
def f():pass
代表着f = d1(d2(f))
7.10参数化装饰器
如果我们想在某些时候传点参数给我们的装饰器该怎么办呢?
还是只能利用闭包,因为真正的装饰器只能有一个参数,获取其他变量只能靠闭包。于是我们需要在真正的装饰器外部再套一个函数
这里迫于时间只列举一个例子:
registry = set()
def register(active = True):
def decorter(func):
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorter
@register()
def f1():pass
这里包了另外一层register,并且在装饰时要先调用register(),返回得到decorter,再@
接下来总结一下{}字符格式的用法:
"{变量名}" 表示输出时这个大括号里的内容只能靠关键词传入
“{name:10s}表示包括name最少占据10个位.