文章目录

  • 一、定义嵌套函数
  • 二、定义闭包函数
  • 三、闭包形成条件
  • 四、何时使用闭包
  • 五、闭包特性拾遗



本文将带你了解:

  • 什么是 Python 闭包?
  • 如何定义一个 Python 闭包?
  • 为什么要使用 Python 的闭包?

一、定义嵌套函数

在了解什么是 Python 闭包之前,我们需要首先理解什么是嵌套函数。

  • 嵌套函数:在一个函数(外部函数)内部定义的函数叫做嵌套函数。嵌套函数可以访问外部函数中定义的变量。

例如,下面的代码演示了在嵌套函数中访问一个非局部变量:

def print_msg(msg):
    # 这是外部函数

    def printer():
        # 这是嵌套函数
        print(msg)

    printer()


# 执行函数,输出"Hello"
print_msg("Hello")

二、定义闭包函数

在上述例子中,如果更改 print_msg 函数的最后一行,将函数调用 printer 改为返回函数名 printer 会怎么样?请运行下列代码:

def print_msg(msg):
    # 这是外部函数

    def printer():
        # 这是嵌套函数
        print(msg)

    return printer


foo = print_msg("Hello")

del print_msg

foo()

上述代码的运行结果依然是 "Hello",原因在于:

  • 调用 print_msg 函数时传入参数 "Hello"
  • print_msg 函数的返回值赋给变量 foo,此时该变量指向 printer 函数对象保存的空间;
  • 在函数 print_msg 执行完毕后, msg 变量依旧保存在内存中(使用 del 关键字显式删除了该函数)。

于是:

闭包是 Python 的一个语言特性,该特性可以使得一些数据(msg)和代码(嵌套函数)绑定在一起。

三、闭包形成条件

由上述代码可知,在 Python 中,如果一个嵌套函数引用了一个定义在外部函数中的非局部变量,则就形成了一个闭包。

更严谨地,形成一个闭包需要以下三个条件:

  • 需要有一个嵌套函数;
  • 嵌套函数使用了定义在外部函数中的非局部变量;
  • 外部函数的返回值必须是嵌套函数的名称。

四、何时使用闭包

闭包可以避免过多使用全局变量,并提供某种程度上的数据隐藏,同时,闭包还可以为某一问题提供类面向对象的解决方案。

最主要的是,有时,如果采用面向对象的方式实现某一需求,可能需实现的实例方法很少。此时,闭包就可以提供一个更加优雅、更节省内存(自定义一个类时,最少会继承object类中的方法,因而实例化自定义类时,所需内存较大)的解决方案。但是,当自定义类的属性和方法很多时,则最好还是定义一个类。

例如:现有这样一个需求,将任意一个数乘以指定的倍数。

(1)使用面向对象的方式:

class Multiplier(object):

    def __init__(self, multiple):
        self.multiple = multiple

    # 当以调用callable的方式调用Multiplier的实例化对象时,
    # __call__方法被自动调用
    def __call__(self, *args, **kwargs):
        print(args[0] * self.multiple)


multiplier3 = Multiplier(3)
multiplier3(5)

multiplier5 = Multiplier(5)
multiplier5(7)

(2)使用闭包的方式:

def make_multiplier_of(multiple):

    def multiplier(num):
        return num * multiple

    return multiplier


# 3的倍数
times3 = make_multiplier_of(3)

# 5的倍数
times5 = make_multiplier_of(5)

print(times3(5))

print(times5(7))

print(times5(times3(2)))

五、闭包特性拾遗

事实上,所有的函数都有一个__closure__属性,当函数为闭包函数时,该属性为一个包含元胞对象(cell object)元组,否则为None,且通过每个元胞对象的cell_contents属性,可以获取存储在闭包中的非局部变量值,如下代码所示:

def foo_outer(argv):
    var = 100

    def foo_inner():
        print(argv + var)

    return foo_inner


foo = foo_outer(200)
foo()
print(foo_outer.__closure__)
print(foo.__closure__)
print("foo.__closure__[0].cell_contents = ", foo.__closure__[0].cell_contents)
print("foo.__closure__[1].cell_contents = ", foo.__closure__[1].cell_contents)

上述代码的运行结果为:

300
None
(<cell at 0x7f251a0fb438: int object at 0xa6a3a0>, <cell at 0x7f2518439078: int object at 0xa69720>)
foo.__closure__[0].cell_contents = 200
foo.__closure__[1].cell_contents = 100

nonlocal关键字:一般来说,嵌套函数只可以访问在外部函数中定义的变量(称为非局部变量)而无法对其进行修改,如果确实需要对非局部变量进行修改,需要在嵌套函数中,对非局部变量使用nonlocal关键字进行显式声明。