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 at 0x0000017D9898B618: int object at 0x000000006D506C20>, <cell at 0x0000017D9898B978: int object at 0x000000006D506C40>)
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
可见,闭包比类更加简洁。