闭包closure

闭包closure
什么是闭包

  闭包的英文是closure,原意是代码和范围的结合。在Python中,闭包指的是将“数据”附加到“一段代码”上的技术。通常“数据”是通过参数传递的实参,而“一段代码”指的是函数。换句话说,闭包就是使得函数记住其创建时的运行环境的技术。
  要理解闭包,需要先理解嵌套函数。如果在一个函数内部,定义了另外一个函数,这种情况就叫做嵌套函数,也叫做函数的嵌套。下面是一个函数嵌套的例子:

def make_multiple(times):
    def multiple(x):
        return x * times
    return multiple

  上面的例子在函数make_multiple()定义的内部,定义了另一个函数multiple(),这种情况就是函数嵌套。
  嵌套函数如何使用的,下面是一个使用嵌套函数的例子:

def make_multiple(times):
    def multiple(x):
        return x * times
    return multiple

times10 = make_multiple(10)
times20 = make_multiple(20)

print(times10(3))
print(times20(3))

  程序输出:

30
60

  在函数multiple()中可以访问其外层函数make_multiple()的变量times,变量times也称为创建函数multiple()时的运行环境。创建times10时,创建函数multiple()的运行环境也就是times的值是10,而对于times20来说,其运行环境也就是times的值是20。从程序输出可以看出,创建函数multiple()时保存了创建时的运行环境,并在后续的调用(times10(3)和times20(3))中使用了保存的运行环境,这种技术就是闭包。

如何创建闭包

  在Python中,闭包需要满足三要素:
    1.定义嵌套函数,包括外层函数和内存函数
    2.内层函数要引用外层函数中的变量
    3.外层函数需要返回内层函数(注意:返回的是内层函数本身,而不是内层函数执行后的返回值)

使用闭包的目的

  从形势上看,闭包主要用来创建不同的函数实例;从本质上看,闭包可以用来避免全局变量的使用,以及实现某种形式的数据隐藏。它提供了与类不同的面向对象的编程。当情况比较简单时,使用闭包是一个比较好的选择;如果情况比较复杂,例如涉及到的数据和函数较多、逻辑复杂、考虑到继承等等,则应该使用类的方式。

闭包与装饰器

  装饰器实际上是闭包的一个应用。参考下面一个装饰器的例子:

import time

def timing(func):
    def wrapper(*arg, **kw):
        start = time.time()
        ret = func(*arg, **kw)
        end = time.time()
        return ret, (end - start)
    return wrapper

@timing
def calculate():
    sum = 0
    for i in range(1000*1000):
        sum += i*i
    return sum

ret, run_time = calculate()
print('calculate toook %fs' % run_time)

  上面这个装饰器的例子可以改写为函数调用的形式,二者是等效的:

import time

def timing(func):
    def wrapper(*arg, **kw):
        start = time.time()
        ret = func(*arg, **kw)
        end = time.time()
        return ret, (end - start)
    return wrapper

def calculate():
    sum = 0
    for i in range(1000*1000):
        sum += i*i
    return sum

calculate = timing(calculate)
ret, run_time = calculate()
print('calculate toook %fs' % run_time)

  可以看出,装饰器实际上是闭包的一种,只不过对于装饰器(timing)来说,保存的运行环境是被装饰器的函数(calculate())。

__closure__

  如果一个函数是一个闭包,那么它的__closure__属性是一个元祖,保存着运行环境。

def make_multiple(times):
    def multiple(x):
        return x * times
    return multiple

times10 = make_multiple(10)
times20 = make_multiple(20)

print('make_multiple.__closure__: ', make_multiple.__closure__)

print('times10.__closure__: ', times10.__closure__)
print('times20.__closure__: ', times20.__closure__)

print('times10.__closure__[0]: %r' % times10.__closure__[0])
print('times20.__closure__[0]: %r' % times20.__closure__[0])

print('times10.__closure__[0].cell_contents: %r' % times10.__closure__[0].cell_contents)
print('times20.__closure__[0].cell_contents: %r' % times20.__closure__[0].cell_contents)

  输出:

make_multiple.__closure__: None
times10.__closure__: (,)
times20.__closure__: (,)
times10.__closure__[0]:
times20.__closure__[0]:
times10.__closure__[0].cell_contents: 10
times20.__closure__[0].cell_contents: 20

  从输出可以看出:对于函数make_multiple()来说,它并不是一个闭包,它的属性__closure__为None。而对于times10和times20来说,它们都是闭包,其属性__closure__保存着一个元组,元组的第一个元素的cell_contents保存着运行时保存的times值,分别为10和20。