Python:闭包(closure)的延迟绑定

看一下这道题:

def fun():
    temp = [lambda x: i * x for i in range(4)]
    return temp


for every_lambda in fun():
    print(every_lambda(2))

猜猜以上代码输出什么?

初看这道题时,我几乎是脱口而出,这不就是0,2,4,6嘛,so easy。

然而…

6
6
6
6	

wtf?简直是6到不行,啪啪打脸。

直接看lambda和列表推导式,或许不大直观,我将它改了一下:

def fun():
    inners = []
    for i in range(4):
        def inner(x):
            return i * x
        inners.append(inner)
    return inners


for inner in fun():
    print(inner(2))

这么来看就清晰直观多了,fun中有个循环,循环生成了4个闭包,每个闭包都引用了自由变量i,然后返回这4个闭包构成的列表。

初看这道题时,我以为inner的自由变量i是直接绑定了嵌套作用域(即for中的i)的值,但分析后发现,inner中的i并不是直接绑定好值的,而是延迟绑定(late binding)!

即只有在inner被调用的时候,inner才会去嵌套作用域查找i的值!
而以上代码中inner被调用时,for循环已经结束,整个嵌套作用域,只存在一个i,它的值是3

知道问题所在之后,我恍然大悟,看来闭包的这个延迟绑定的特性,的确有点坑人啊。

那么,如果我如何才能让整个坑人的闭包,返回我真正想要的0,2,4,6呢?

这里提供两种方式:

方式1:设置默认参数

def fun():
    inners = []
    for i in range(4):
        def inner(x, i=i):	# 使用默认参数的方式,将for循环取出的i立即绑定到默认参数上
            return i * x
        inners.append(inner)
    return inners

这种方式看似没有改变代码结构,但实际上inner已经不是闭包了.
inner函数体中的变量i并不是自由变量,不会去外部作用域中查找,它就是自身参数定义中已经设置好的默认参数。
Python中一切皆对象,函数也是一个对象,它拥有__default__属性,返回的是默认参数构成的元组,拥有__closure__属性,返回的是闭包所引用的自由变量。

def fun():
    inners = []
    for i in range(4):
        def inner(x, i=i):	# 使用默认参数的方式,将for循环取出的i立即绑定到默认参数上
            return i * x
        inners.append(inner)
    return inners


for inner in fun():
    print(inner.__defaults__, inner.__closure__)
    print(inner(2))
(0,) None
0
(1,) None
2
(2,) None
4
(3,) None
6

很明显,__closure__返回的是None,说明此时inner已经不是闭包了。

方式二:生成器

def fun():
    for i in range(4):
        def inner(x):
            return i * x
        yield inner	# 由之前的一次性返回4个闭包,改为一次返回1个闭包

for inner in fun():
    print(inner.__closure__)
    print(inner(2))
(<cell at 0x00000138CBEF65E8: int object at 0x0000000061E3BBF0>,)
0
(<cell at 0x00000138CBEF65E8: int object at 0x0000000061E3BC10>,)
2
(<cell at 0x00000138CBEF65E8: int object at 0x0000000061E3BC30>,)
4
(<cell at 0x00000138CBEF65E8: int object at 0x0000000061E3BC50>,)
6

但方式二存在一个问题,当用list构造函数先行将生成器构造好一个列表,然后再调用列表中的闭包,结果仍是6,6,6,6

def fun():
    for i in range(4):
        def inner(x):
            return i * x
        yield inner	# 由之前的一次性返回4个闭包,改为一次返回1个闭包


inners = list(fun())
for inner in inners:
    print(inner(2))
6
6
6
6

说了这么多,总结下来,闭包的延迟绑定,就是只在闭包被调用时,才会去寻找闭包所引用的自由变量

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包在编程中有很多应用场景,以下是一些常见的例子: 1. 数据封装和私有变量:闭包可以用来创建私有变量,从而实现数据封装和保护。这在模块化编程和面向对象编程中非常有用。 2. 函数工厂:闭包可以用来创建一系列相关的函数,这些函数共享相同的外部变量。这在创建类似于Python中的装饰器或JavaScript中的高阶函数时非常有用。 3. 延迟执行和计时器:闭包可以用来实现延迟执行和定时器功能。例如,在JavaScript中,setTimeout和setInterval函数使用闭包来实现延迟执行和定时器功能。 4. 记忆化(Memoization):闭包可以用来实现记忆化,即缓存函数的计算结果,以便在后续调用中重用。这可以提高函数的性能,特别是在处理计算密集型任务时。 5. 事件处理和回调函数:在JavaScript等事件驱动的编程环境中,闭包常用于实现事件处理和回调函数。闭包可以捕获事件处理函数的上下文,使得事件处理函数可以访问其所需的外部变量。 6. 部分应用(Partial Application)和柯里化(Currying):闭包可以用来实现部分应用和柯里化,这是一种将多参数函数转换为一系列单参数函数的技术。这可以简化函数调用,使得代码更加简洁和可读。 7. 实现迭代器和生成器:在某些编程语言中,例如Python和JavaScript,闭包可以用来实现迭代器和生成器,这是一种用于遍历数据结构的高效方法。
07-14

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值