Skip to content

理解Python闭包

Info

作者:Vincent,发布于2021-09-21,阅读时间:约3分钟,微信公众号文章链接:

1 前言

在使用Python的日常工作中,你或许碰到过类似于这样的代码:

def make_counter():
    # 外层闭包函数
    count = 0
    def counter():
      # 嵌套函数

        nonlocal count
        count += 1
        return count

    return counter

一个函数里还有一个函数,并且外层函数的返回值是内层的函数,为什么要这样定义函数呢?这样有什么好处,这篇文章我们来揭开它神秘的面纱 - 闭包(Closure)。

2 闭包的要点

闭包指延伸了作用域的函数,它会引用一个不在函数定义中的非全局变量(如上述例子中的count),nonlocal的加入能把变量标记为自由变量(Python 3才加入nonlocal关键字),让内层嵌套函数可以修改作用域外的不可变变量。

调用make_counter时,返回一个counter函数对象,每次调用counter时,它会更新count,示例如下:

# 运行闭包函数
counter = make_counter()
print(counter())
print(counter())

输出为:

1
2

在这个例子中,有一点需要展开说的是count的历史值的存储位置,count是make_counter函数的局部变量,初始化的时候count的值为0,但调用counter的时候,make_counter函数已经返回了,本地作用域理应不复存在。

在counter函数中,count是自由变量,counter函数实现了对变量count的绑定。可以通过Python中的__code__属性(表示编译后的函数定义体)查看保存的局部变量和自由变量的名称,如下所示:

# 查看自由变量
counter.__code__.co_freevars

输出为:

('count',)

count的绑定在返回的counter函数的__closure__属性里,其中__closure__的各个元素对应于counter.__code__.co_freevars中的一个名称。这些元素是cell对象,可以通过cell_contents属性访问其存储的值,示例如下:

counter.__closure__[0].cell_contents

输出为:

2

闭包能够非常简洁、直观地解决轻量级的问题,如果上述功能用来实现的话,会长成这样:

# 用类定义一个计数器,从0开始计数
class Counter:
    def __init__(self):
        self.count = 0

    def __call__(self):
        self.count += 1
        return self.count

counter = Counter()
print(counter())
print(counter())

输出为:

1
2

3 总结

闭包是一种函数,它保留了定义函数时存在的自由变量的绑定,因此,即便是函数返回后作用域不存在了,那些绑定仍然能够被使用。闭包可以很容易地实现一些简单的类的功能,同时基于此还有很多Python的魔法能够实现,比如装饰器,待下回分解!

希望这次的分享对你有帮助,欢迎在评论区讨论!


Viewed times

Comments