Python 闭包

这篇文章翻译自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-Closure-Example.png

返回一个内部函数

在Python中,一个函数可以返回一个值,这个值是另一个函数的函数名。比如说:

def say():
    greeting = 'Hello'

    def display():
        print(greeting)

    return display

在这个例子中, say 函数返回 display 函数,而不是执行它。

同时,当 say 函数返回 display 函数时,它实际上返回了一个闭包:

Python-Closure-Example.png

下面将 say 函数的返回值分配给一个变量 fn 。由于 fn 是一个函数,你可以执行它。

fn = say()
fn()

输出:

Hello

say 函数会执行并返回一个函数。当 fn 函数执行时, say 函数已经完成了。

换句话说, say 函数的作用域在 fn 函数执行时已经消失。

由于 greeting 变量属于 say 函数的作用域,它也应该随着函数的作用域被销毁。

然而,你仍然看到 fn 显示的是 message 变量的值。

Python cells 和 多作用域变量

greeting 变量的值是在这两个作用域之间共享的:

  • say 函数

  • 闭包

标签 greeting 在两个不同的作用域中。然而,它们总是引用同一个字符串对象,其值为 ‘Hello’

要实现这一点,Python创建了一个叫做 cell 的中间对象。

Python-Closures.jpg

要找到 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/)
---
---
转载请注明译者和原出处,请勿用于任何商业用途。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值