一步一步教你认识 Python 闭包

我在博客中曾经介绍过两篇关于函数的文章,第一篇是 关于 Python 函数是第一类对象,第二篇是关于 Lambda 函数,今天来讲说 Python 闭包。html

什么是闭包?闭包有什么用?为何要用闭包?今天咱们就带着这3个问题来一步一步认识闭包。python

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

做用域

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

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

num = 10 # 全局做用域变量
def foo():
    print(num)  # 10复制代码

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

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

嵌套函数

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

def print_msg():
    # print_msg 是外围函数
    msg = "zen of python"

    def printer():
        # printer是嵌套函数
        print(msg)
    printer()
# 输出 zen of python
print_msg()复制代码

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

那么有没有一种可能即便脱离了函数自己的做用范围,局部变量还能够被访问获得呢?答案是闭包code

什么是闭包

函数身为第一类对象,它能够做为函数的返回值返回,如今咱们来考虑以下的例子:cdn

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中了。

同步发表博客:foofish.net/python-clos…
公众号:Python之禅 (id:VTtalk),分享 Python 等技术干货

Python之禅
相关文章
相关标签/搜索