闭包
闭包是延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。
函数与是不是匿名函数无关,关键是他能访问定义体之外定义的非全局变量。
class Avarage():
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)
avg = Avarage()
avg(10)
10.0
avg(11)
10.5
avg(12)
11.0
def make_average():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
avg = make_average()
avg(10)
10.0
avg(11)
10.5
avg(12)
11.0
前面一个是类的实例调用,后者是函数式的实现方式。
在第二种方式中,average的闭包延伸至函数的作用域之外,包含自由变量series的绑定。
avg.__code__.co_varnames
('new_value', 'total')
avg.__code__.co_freevars
('series',)
avg.__closure__
(<cell at 0x10c63e198: list object at 0x10c4db308>,)
avg.__closure__[0].cell_contents
[10, 11, 12]
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
nonlocal声明
>>> def make_averager():
... count = 0
... total = 0
... def averager(new_value):
... count += 1
... total += new_value
... return total/ count
... return averager
...
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in averager
UnboundLocalError: local variable 'count' referenced before assignment
在上面这个示例中,count虽然在make_averager中定义了,但是在averager函数中,由于count被重新赋值了,导致count成了局部变量。
之前那个例子中,用到了series.append()的方式,并非赋值,因此没有出现报错。
但是对于数字、字符串、元组等不可变的类型来说,只能读取,不能更新。如果尝试重新绑定赋值,那么会隐式的创建一个局部变量。因此,count不再是一个自由变量了,也不会保存在闭包中。
为了解决这个问题,Python3中引入了nonlocal声明,作用就是把变量标记成自由变量。
如果为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
...
>>> avg = make_averager()
>>> avg(10)
10.0
如上示例,此时再调用averager()方法就不会报错了。
实现一个简单的装饰器
>>> import time
>>> def clock(func):
... def clocked(*args):
... t0 = time.perf_counter()
... result = func(*args)
... elapsed = time.perf_counter() - t0
... name = func.__name__
... print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
... return result
... return clocked
输出如下:
>>> @clock
... def snooze(seconds):
... time.sleep(seconds)
...
>>> @clock
... def factorial(n):
... return 1 if n < 2 else n*factorial(n-1)
...
>>> if __name__ == '__main__':
... print('*' * 40, 'Calling snooze(.123)')
... snooze(.123)
... print('*' * 40, 'Calling factorial(6)')
... print('6! =', factorial(6))
...
**************************************** Calling snooze(.123)
[0.12820711s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000095s] factorial(1) -> 1
[0.00002918s] factorial(2) -> 2
[0.00004905s] factorial(3) -> 6
[0.00006645s] factorial(4) -> 24
[0.00008395s] factorial(5) -> 120
[0.00011178s] factorial(6) -> 720
6! = 720
示例中,factorial保存的是clocked函数的引用。自此之后,每次调用factorial(n),执行的都是clocked(n)。
装饰器的典型行为:
把被装饰的函数替换成新函数,二者接受相同的参数,而且通常返回被装饰的函数本该返回的值,同时还会做些额外的操作。