闭包函数
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。——维基百科
闭包函数的条件
- 闭包函数必须有内嵌函数
- 闭包函数必须引用外层函数的变量
- 闭包函数返回内嵌函数的地址(函数名称)
闭包函数示例
def first():
age = 10
def second(): # 内嵌函数
print(age) # 引用外层函数的变量
return second # 返回内嵌函数的函数名
我们称上述函数second为一个闭包函数
判断一个函数是否为闭包
我们可以通过函数名.closure 在函数为闭包时,返回一个cell ,如果不是闭包函数则返回None
def first():
age = 10
def second(): # 内嵌函数
print(age) # 引用外层函数的变量
print(second.__closure__)
return second # 返回内嵌函数的函数名
>>> s = first()
>>> s()
(<cell at 0x0000027BBE7A7318: int object at 0x00007FF9120FFB60>,)
10
def first():
age = 10
def second(): # 内嵌函数
print(10) # 引用外层函数的变量
print(second.__closure__)
return second # 返回内嵌函数的函数名
>>> s = first()
>>> s()
None
10
second不是闭包函数,因为没有引用外层函数的变量
def first(age):
def second(): # 内嵌函数
print(age) # 引用外层函数的变量
print(second.__closure__)
return second # 返回内嵌函数的函数名
>>> s = first(100)
>>> s()
(<cell at 0x0000016A608D6408: int object at 0x00007FF9120FFB60>,)
100
second是闭包函数,引用了外层函数的参数
闭包函数的作用
开篇说到闭包函数被多次调用的过程中,其引用的外层变量能够保持其持久性,我们以一个实例来理解这一点。
假如有一个名为avg的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新的价格,因此平均值要考虑目前为止所有的价格。
实现方式一
class Averager():
def __init__(self):
self.series = []
def __call__(self, new_val):
self.series.append(new_val)
total = sum(self.series)
return total / len(self.series)
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
实现方式二
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)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
nonlocal声明
avg函数的另一种实现
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):
...
UnboundLocalError: local variable 'count' referenced before assignment
问题是,当count是数字或者任何不可变类型时,count += 1语句的作用其实与count = count + 1一样。因此,我们在averager的定义体中为count赋值了,这会把count变成局部变量。total变量也受这个问题影响。
上一个代码片段没有出现这个问题,因为我们没有给series赋值,我们只是调用series.append,并把它传递给sun和len。也就是说,我们利用了列表是可变对象这一事实。
但是对于数字、字符串、元组等不可变对象来说,只能读取,不能更新。如果尝试重新绑定,例如count = count + 1,其实会隐式创建局部变量count,这样,count就不是自由变量了,因此不会保存在闭包中。为了解决这个问题,python3引入了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