这篇文章翻译自https://www.pythontutorial.net/advanced-python/python-closures/
Python 闭包
摘要:在本教程中,你将学习 Python 闭包和它们的实际应用。
Python 闭包的介绍
在 Python 中,你可以在一个函数的内部定义一个函数。而这个函数被称为嵌套函数。比如说:
def say():
greeting = 'Hello'
def display():
print(greeting)
display()
在这个例子中,我们在 say 函数中定义了 display 函数。 display 函数被称为一个嵌套函数。
在 display 函数中,你从其非局部作用域访问 greeting 变量。
Python 称 greeting 变量为自由变量。
当你看 display 函数时,你实际上是在看:
-
display 函数本身
-
自由变量 greeting 的值为 ‘Hello’
所以, display 函数和 greeting 变量的组合被称为封闭。
返回一个内部函数
在Python中,一个函数可以返回一个值,这个值是另一个函数的函数名。比如说:
def say():
greeting = 'Hello'
def display():
print(greeting)
return display
在这个例子中, say 函数返回 display 函数,而不是执行它。
同时,当 say 函数返回 display 函数时,它实际上返回了一个闭包:
下面将 say 函数的返回值分配给一个变量 fn 。由于 fn 是一个函数,你可以执行它。
fn = say()
fn()
输出:
Hello
say 函数会执行并返回一个函数。当 fn 函数执行时, say 函数已经完成了。
换句话说, say 函数的作用域在 fn 函数执行时已经消失。
由于 greeting 变量属于 say 函数的作用域,它也应该随着函数的作用域被销毁。
然而,你仍然看到 fn 显示的是 message 变量的值。
Python cells 和 多作用域变量
greeting 变量的值是在这两个作用域之间共享的:
-
say 函数
-
闭包
标签 greeting 在两个不同的作用域中。然而,它们总是引用同一个字符串对象,其值为 ‘Hello’ 。
要实现这一点,Python创建了一个叫做 cell 的中间对象。
要找到 cell 对象的内存地址,你可以使用 __closure__
属性,如下所示。:
print(fn.__closure__)
输出:
(<cell at 0x0000017184915C40: str object at 0x0000017186A829B0>,)
__closure__
返回一个 cell 的元组。
在这个例子中,单元格的内存地址是 0x0000017184915C40 。 它在 0x0000017186A829B0 处引用一个字符串对象。
如果你在 say 函数和 closure 中显示字符串对象的内存地址,你应该看到它们引用了内存中的同一个对象。
def say():
greeting = 'Hello'
print(hex(id(greeting)))
def display():
print(hex(id(greeting)))
print(greeting)
return display
fn = say()
fn()
输出:
0x17186a829b0
0x17186a829b0
当你访问 greeting 变量的值时,Python 会巧妙的 “双跳” 以获得字符串的值。
这就解释了为什么当 say 函数 函数超出范围时,你仍然可以访问 greeting 变量所引用的字符串对象。
基于这种机制,你可以把闭包看成是一个函数和一个包含自由变量的扩展作用域。
要找到一个闭包所包含的自由变量,你可以使用__code__.co_freevars
,例如。
def say():
greeting = 'Hello'
def display():
print(greeting)
return display
fn = say()
print(fn.__code__.co_freevars)
输出:
('greeting',)
在这个例子中, fn.__code__.co_freevars
返回 fn 闭包的 greeting 自由变量。
什么时候创建闭包
当一个函数执行时,Python 会创建一个新的作用域。如果该函数创建了一个闭包,Python 也会创建一个新的闭包。考虑一下下面的例子:
首先,定义一个名为 multiplier 的函数,返回一个闭包。
def multiplier(x):
def multiply(y):
return x * y
return multiply
multiplier 函数返回两个参数的乘法。然而,它使用了一个闭包来代替。
然后,调用 multiplier 函数三次。
m1 = multiplier(1)
m2 = multiplier(2)
m3 = multiplier(3)
这些函数调用创建了三个闭包。每个函数将一个数字与1、2、3相乘。
最后,执行闭包的功能:
print(m1(10))
print(m2(10))
print(m3(10))
输出:
10
20
30
m1、m2和m3是不同的闭包实例。
Python 闭包和 for 循环
假设你想同时创建上面的三个封闭,你可能会想到下面的方法:
multipliers = []
for i in range(1, 4):
multipliers.append(lambda x: i * x)
m1, m2, m3 = multipliers
print(m1(10))
print(m2(10))
print(m3(10))
它是如何工作的。
-
首先,声明一个将存储闭包的列表。
-
第二,使用 lambda 表达式来创建一个闭包,并在每次循环中把闭包追加到列表中。
-
第三,将列表中的闭包解包到m1、m2和m3变量。
-
最后,将数值10、20和30传递给每个闭包,并执行它。
以下是输出结果:
30
30
30
这并不像你预期的那样工作。但为什么呢?
循环中的 x 从 1 开始到 3。循环结束后,其值为 3。
列表中的每个元素都是以下的封闭:
lambda y: x*y
当你调用 m1(10) 、 m2(10) 和 m3(10) 时,Python会求 x 的值 。在闭包执行的时候, x 是3。
这就是为什么你在调用当你调用 m1(10) 、 m2(10) 和 m3(10) 时看到相同的结果。
为了解决这个问题,你需要指示 Python 在循环中计算 x 的值。
def multiplier(x):
def multiply(y):
return x * y
return multiply
multipliers = []
for x in range(1, 4):
multipliers.append(multiplier(x))
m1, m2, m3 = multipliers
print(m1(10))
print(m2(10))
print(m3(10))
总结
闭包是一个函数和一个包含自由变量的扩展作用域。
(全文完)
部分图片来自于源网站,侵删。
鉴于本人才疏学浅,翻译难免有所疏漏,如果有任何问题欢迎随时联系我进行批评指正:2076577077@qq.com
我是gled fish, [点击这里来到我的博客网站:](https://gledfish.netlify.app/)
---
---
转载请注明译者和原出处,请勿用于任何商业用途。