# 闭包

# 闭包

  • 在一个内部函数中,对外部作用域的变量进行引用, (并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包 (colsure)​。

  • 闭包是支持一等函数的编程语言(Python、JavaScript等)中实现词法绑定的一种技术。

    • 函数成为一等公民是指函数可以不依附于任何类或对象等实体而独立存在,它可以单独作为参数、变量或返回值在程序中传递。
    • 当捕捉闭包的时候,它的自由变量(在函数外部定义但在函数内部使用的变量)会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。
    • 简单的说,可以将闭包理解为能够读取其他函数内部变量的函数
    • 正常情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展
    • 使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用
  • 函数闭包中外部函数返回的是一个函数,而不是一个具体的值。

    • 返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。
    • 闭包必须有一个内嵌函数
    • 内嵌函数必须引用外部函数中的变量
    • 外部函数的返回值必须是内嵌函数(函数名仅仅是个变量,指向了定义的函数)
    • 注意外部函数中的变量并不会随着函数的运行而销毁,类似于类中的类变量
  • 闭包常和装饰器放在一起使用

def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是exponent_of函数

square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方 
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3

# 闭包示例

def multiply():
    return [lambda x: i * x for i in range(4)]

print([m(100) for m in multiply()])

运行结果:

[300, 300, 300, 300]

上面代码的运行结果很容易被误判为[0, 100, 200, 300]​。首先需要注意的是multiply​函数用生成式语法返回了一个列表,列表中保存了4个Lambda函数,这4个Lambda函数会返回传入的参数乘以i​的结果。需要注意的是这里有闭包(closure)现象,multiply​函数中的局部变量i​的生命周期被延展了,由于i​最终的值是3​,所以通过m(100)​调列表中的Lambda函数时会返回300​,而且4个调用都是如此。

如果想得到[0, 100, 200, 300]​这个结果,可以按照下面几种方式来修改multiply​函数。

方法一:使用生成器,让函数获得i​的当前值。

def multiply():
    return (lambda x: i * x for i in range(4))

print([m(100) for m in multiply()])

或者

def multiply():
    for i in range(4):
        yield lambda x: x * i

print([m(100) for m in multiply()])

方法二:使用偏函数,彻底避开闭包。

from functools import partial
from operator import __mul__

def multiply():
    return [partial(__mul__, i) for i in range(4)]

print([m(100) for m in multiply()])