V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lzjun
V2EX  ›  Python

对 Python 闭包的理解

  •  1
     
  •   lzjun ·
    lzjun567 · 2017-05-15 11:33:20 +08:00 · 3157 次点击
    这是一个创建于 2509 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前曾经介绍过两篇关于函数的文章,一篇是关于 Python 函数是第一类对象,第二篇是关于 Lambda 函数,而 Python 作为函数中的另一个高级话题,是一个比较难搞懂的概念。

    什么是闭包?闭包有什么用?为什么要用闭包?带着这 3 个问题来一步一步认识闭包。

    闭包和函数紧密联系在一起,介绍闭包前有必要先介绍一些背景知识,诸如嵌套函数、变量的作用域等概念

    作用域

    作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。

    定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:

    num = 10 # 全局作用域变量
    def foo():
        print(num)  # 10
    

    而在函数外部则不可以访问局部变量。例如:

    def foo():
        num = 10
    print(num)  # NameError: name 'num' is not defined
    

    嵌套函数

    函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部,像这种定义在函数里面的函数称之为嵌套函数( nested function )例如:

    def print_msg():
    	# print_msg 是外围函数
    	msg = "zen of python"
    
        def printer():
        	# printer 是嵌套函数
            print(msg)
        printer()
    # 输出 zen of python
    print_msg()
    
    

    对于嵌套函数,它可以访问到其外层作用域中声明的非局部( non-local )变量,比如代码示例中的变量 msg 可以被嵌套函数 printer 正常访问。

    那么有没有一种可能即使脱离了函数本身的作用范围,局部变量还可以被访问得到呢?答案是闭包

    什么是闭包

    函数身为第一类对象,它可以作为函数的返回值返回,现在我们来考虑如下的例子:

    def print_msg():
    	# print_msg 是外围函数
    	msg = "zen of python"
        def printer():
        	# printer 是嵌套函数
            print(msg)
        return printer
    
    another = print_msg()
    # 输出 zen of python
    another()
    

    这段代码和前面例子的效果完全一样,同样输出 "zen of python"。不同的地方在于内部函数 printer 直接作为返回值返回了。

    一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 print_msg() 执行过后,我们会认为 msg变量将不再可用。然而,在这里我们发现 print_msg 执行完之后,在调用 another 的时候 msg 变量的值正常输出了,这就是闭包的作用,闭包使得局部变量在函数外被访问成为可能。

    看完这个例子,我们再来定义闭包,维基百科上的解释是:

    在计算机科学中,闭包( Closure )是词法闭包( Lexical Closure )的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

    这里的 another 就是一个闭包,闭包本质上是一个函数,它有两部分组成,printer 函数和变量 msg。闭包使得这些变量的值始终保存在内存中。

    闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

    为什么要使用闭包

    闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

    一般来说,当对象中只有一个方法时,这时使用闭包是更好的选择。来看一个例子:

    def adder(x):
        def wrapper(y):
            return x + y
        return wrapper
    
    adder5 = adder(5)
    # 输出 15
    adder5(10)
    # 输出 11
    adder5(6)
    

    这比用类来实现更优雅,此外装饰器也是基于闭包的一中应用场景。

    所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。cell 对象的 cell_contents 属性就是闭包中的自由变量。

    >>> adder.__closure__
    >>> adder5.__closure__
    (<cell at 0x103075910: int object at 0x7fd251604518>,)
    >>> adder5.__closure__[0].cell_contents
    5
    

    这解释了为什么局部变量脱离函数之后,还可以在函数之外被访问的原因的,因为它存储在了闭包的 cell_contents中了。

    7 条回复    2017-05-15 13:55:33 +08:00
    AndyMo
        1
    AndyMo  
       2017-05-15 12:06:36 +08:00 via Android
    学习了👍
    cashew
        2
    cashew  
       2017-05-15 12:17:04 +08:00 via Android
    我关注了这个公众号。
    nujabse
        3
    nujabse  
       2017-05-15 12:21:16 +08:00 via Android
    一直以来在读楼主的博客文章,写得很清晰易懂,受教了!
    bramblex
        4
    bramblex  
       2017-05-15 12:43:13 +08:00 via iPhone   ❤️ 3
    闭包的运作用就是对的数据或者操作的复合,跟对象的作用是一样样的。说真的,对于 python 这种面对对象特性完善,函数式特性半残,甚至官方都比较反对函数式的语言来说,能用对象尽量用对象,包括装饰器也可以写成装饰器类的形式,能用好基本的 lambda 就足够了。不是大量使用高阶函数的场景,闭包其实都差不多就是局部作用域而已,并且 python 也没有 JavaScript 大量那种坑死人的闭包+异步混合坑,所以搞 python 的哪怕不知道闭包,也不会妨碍好好搬砖的。

    当然这些话不针对楼主,我只是发表一些对 python 的看法而已。楼主发教程贴分享高级技巧,自然是要点一万个赞的👍🏻
    sagaxu
        5
    sagaxu  
       2017-05-15 12:49:31 +08:00
    理解 LEGB 就够了,很直观
    hsmocc
        6
    hsmocc  
       2017-05-15 12:58:52 +08:00 via iPhone
    @bramblex 非常同意你的观点,能简单的还是简单的好,都是人造的东西。
    fxxkgw
        7
    fxxkgw  
       2017-05-15 13:55:33 +08:00
    阿里嘎多。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3337 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 42ms · UTC 00:42 · PVG 08:42 · LAX 17:42 · JFK 20:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.