什么是闭包
用一句话粗略概况为:在一个函数内,读取外部函数定义的变量的机制。更一般地说,闭包函数是带有状态的函数,状态是指调用环境的上下文,当函数带上了状态就是闭包。
如下代码,在函数f
内定义了一个嵌套的函数inner
,inner
内往f
函数内定义的data
中追加元素,可以称inner
内使用data
的机制为闭包,由于inner
是一个函数,只有调用时才会真正执行,固f
一般将inner
作为返回值,或者更一般地称函数f
返回了一个闭包。
def f():
data = [] # 闭包函数外的函数中
def inner(value): # 局部作用域
data.append(value)
return data
return inner
g = f()
print(g(1)) # 输出[1]
print(g(2)) # 输出[1, 2]
闭包是如何实现的
作用域回顾
python一共有四种作用域,分别是:L (Local) 局部作用域;E (Enclosing) 闭包函数外的函数中;G (Global) 全局作用域;B (Built-in) 内建作用域,规则顺序: L –> E –> G –> B。在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。在inner
函数中查找data
时,会先在局部作用域寻找,而局部作用域没有data
的定义,转而到闭包函数内找到data
的定义,故可以确定,innner
内使用的data
是在外部函数定义的。
x = int(2.9) # 内建作用域,查找int函数
data = 0 # 全局作用域
def f():
data = [] # 闭包函数外的函数中
def inner(value): # 局部作用域
data.append(value)
return data
return inner
内部函数使用外部函数的data时,是拷贝还是引用?
Q:inner
函数内引用的data
是函数f
中定义data
的copy?还是f
和inner
引用的是同一个data
变量?
A:首先给出正面的回答,内层函数与外层函数引用的是同一个变量,可以作以下实验,在f
定义inner
后,修改data
(重新赋值),仍然会影响到inner
内的data
。
def f():
data = [] # 闭包函数外的函数中
def inner(value): # 局部作用域
data.append(value)
return data
data = [1] # 在inner后修改data
return inner
g = f()
print(g(2)) # 输出[1, 2]
print(g(3)) # 输出[1, 2, 3]
闭包变量的值保存在哪?
如果简单把data
理解为f
的局部变量,局部变量保存在栈上,data
只会随f
的调用构建,f
结束时销毁。但事实远非如此,首先data
并不是简单的局部变量,data
在调用一次f
函数后,甚至对函数f
的引用都没有了,data
仍然可以被inner
函数访问。由于Python为引用计数机制,因此一定存在对象保持对data
的引用,那么是谁引用着data
?
实际上data是被保存在Cell Objects里(Cell引用data),以下是Cell Object官方的解释。
- “Cell” objects are used to implement variables referenced by multiple scopes. For each such variable, a cell object is created to store the value; the local variables of each stack frame that references the value contains a reference to the cells from outer scopes which also use that variable. When the value is accessed, the value contained in the cell is used instead of the cell object itself.
“Cell”对象用于实现由多个作用域引用的变量。对于每个这样的变量,都会创建一个Cell对象来存储值;引用该变量的局部变量都包含对外部作用域中也使用该变量的Cell对象的引用。访问该值时,将使用单元格中包含的值,而不是单元格对象本身。单元格对象的这种取消引用需要生成的字节码的支持;访问时不会自动取消引用。单元格对象在其他地方可能没用。
Cell引用data哪谁引用Cell?Cell活在哪(保存在哪)?实际上在调用f
时,会构建function Object
inner
,由于使用了闭包变量data
,在构建inner
时,会设置function object
的闭包变量__closure__
,这个闭包变量__closure__
引用着Cell Object,而Cell引用着data,如下示例。
def f():
data = [] # 闭包函数外的函数中
def inner(value): # 局部作用域
data.append(value)
return data
data = [1]
return inner
enclosure = f()
print(enclosure.__closure__) #(<cell at 0x7f7642bebbe0: list object at 0x7f7642aa3c40>,)
print(enclosure(2)) # [1, 2]
print(hex(id(enclosure(3)))) # 0x7f7642aa3c40
总结
**闭包允许函数定义自己函数内的“全局”变量,**做到长久保存而不污染全局命名空间,即如在f
函数内定义新的函数时,f
内新定义的函数可以像使用全局变量一样使用f函数内定义的变量。
闭包的核心作用是建立一个独立的scope
,做到变量隔离,装饰器是闭包的一大应用。
后记
注:闭包不是Python特有的概念,而是广泛出现在各种编程语言中,如C++ lambda也可以实现闭包。C++中的通过lambda捕获闭包变量时,不仅可以进行引用捕获,还可以进行值捕获,Python的闭包默认为**[&](){}
形式的引用捕获**。
int main() {
int round = 2;
auto f = [=](int f) -> int { return f + round; } ;
cout << "result = " << f(1) << endl;
return 0;
}
参考资料
https://www.liujiangblog.com/course/python/32
https://www.bilibili.com/video/BV1Ab4y1P7ew/?spm_id_from=333.788&vd_source=36208a91e67eaf84fb870cccb7dead12
https://en.cppreference.com/w/cpp/language/lambda
https://zhuanlan.zhihu.com/p/453787908