闭包﹝Python﹞

Closures

转载须注明出处:@Orca_J35 | GitHub@orca-j35,所有笔记均托管于 python_notes 仓库

内函数最重要的一个特性是会自动执行闭包操作。我们都知道内层函数可以引用外层函数的局部变量(或参数),这些被内层函数引用的非局部变量会被绑定到内层函数的 __closure__ 属性中。将非局部变量绑定到内层函数中的过程被称为"闭包",经历过闭包操作的函数被称为"闭包函数",被绑定的非局部变量被称为闭包函数的"自由变量"。

def sum_():  # sum_ 是外函数
    x = 1
    y = 2

    def inner():  # inner 是内函数
        return x + y # x和y是自由变量
    # 内函数经历过闭包,其__closure__属性是由cell组成的元组
    print("__closure__:", inner.__closure__)
    print("cell_contents:", inner.__closure__[0].cell_contents)

sum_()
print(sum_.__closure__)
'''Out:
__closure__: (, )
cell_contents: 1
None
'''

函数对象的 __closure__ 属性与闭包操作相关,如果某个函数经历过闭包的话(比如,inner),那么该属性的值是一个由 cell 对象组成的元组;否则是 None (比如,sum_)。cell 对象的 cell_contents 属性用于绑定自由变量的值。

函数对象作返回值

函数在 Python 中是第一类对象,可作为另一个函数的返回值(注意,被返回的是函数对象,并且函数对象在返回过程中不会被执行)。现在我们来考虑如下例子:

def generate_power(power):
    def nth_power(number):
        return number ** power
    return nth_power
'''
>>> two_power = generate_power(2) # 仅返回函数对象
>>> two_power(5) # 调用被返回的函数对象
25
>>> three_power = generate_power(3)
>>> three_power(4)
64
'''

内函数被返回后,虽然外函数的生命周期已结束,但由于内函数经历过闭包,所以内函数中已绑定了自由变量。因此,从外部调用内函数时,power 参数依旧可用。

另外,内函数是在外函数被调用后才被创建的,所以每次返回的内函数均有不同的 id。即便使用相同的参数调用外层函数,也是如此。

>>> two_power = generate_power(2)
>>> two_power_ = generate_power(2)
>>> two_power is two_power_
False # 即便参数相同,也会返回两个不同id的函数

当我们在外部调用内函数时,外层函数已执行完毕,自由变量均处于外函数执行完毕时的状态。也就是说,如果我们在创建内函数后,又修改过自由变量的话,当我们在外部调用内函数时,会使用已被修改过的自由变量。考虑下面的示例:

def count():
    funcs = []
    for i in [1, 2, 3]: # 每次循环均会修改自由变量i
        # 每次循环都会创建一个内函数,并将其保存到funcs中
        def f(): 
            return i
        funcs.append(f)
    return funcs
# count执行完毕时,变量i的值是3,所以三个函数对象对象中的i都等于3
"""
>>> f1, f2, f3 = count()
>>> f1(), f2(), f3()
(3, 3, 3)
"""

i 是内函数 f 的自由变量,在外函数退出时等于 3。当我们调用 funcs 列表中存放的内函数时,由于自由变量 i 的值是 3,所以内函数的返回值也都是 3。因此,我们应避免在闭包函数中引用将来会发生变化的自由变量。如果非要使用这样的变量,可采用如下方案:再创建一层函数,通过调用该层函数来锁定最内层函数的自由变量。

def count():
    funcs = []
    for i in [1, 2, 3]:
        def g(param):
            f = lambda : param    # 这里创建了一个匿名函数
            return f
        funcs.append(g(i))        # 将循环变量的值传给 g
    return funcs
"""
>>> f1, f2, f3 = count()
>>> f1(), f2(), f3()
(1, 2, 3)
"""

模拟类和实例

"闭包操作"可将函数和数据环境(自由变量)进行绑定,这一点与类非常相似。我们可以利用闭包操作来模拟类和实例。一般来说,当类中只有一个方法时,换用闭包函数是更好的选择。

下面这个类,用于求解定点间的距离:

from math import sqrt


class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    def get_distance(self, u, v):
        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
        return distance


pt = Point(7, 2)
print(pt.get_distance(10, 6)) # Out: 5.0

用闭包操作实现上面的类:

def point(x, y):
    def get_distance(u, v):
        return sqrt((x - u) ** 2 + (y - v) ** 2)

    return get_distance


pt = point(7, 2)
print(pt(10, 6)) # Out: 5.0

可见,闭包比类更加简洁。

参考

  • Python Inner Functions—What Are They Good For?
  • 一步一步教你认识Python闭包 - PYTHON之禅
  • Python 的闭包和装饰器 - Python 学习之旅
  • 闭包 - Python 之旅

你可能感兴趣的:(闭包﹝Python﹞)