闭包 Closures
嵌套函数(nested function),内函数引用了外函数的临时变量,并且外函数返回内函数的引用,这样就构成了一个闭包。
def outer():
x, y, z = 10, 'abc', [1,2]
def inner():
print(x,y)
return inner
f = outer()
print(f.__closure__)
# cell tuple 包含了内函数对外函数的变量 x, y 的引用,但不含 z。
# (<cell at 0x...: int object at 0x...>, <cell at 0x...: str object at 0x...>)
1、Nonlocal variable in a nested function
nested function: 嵌套函数 一个函数定义在另一个函数内部
nested function 可以访问 enclosing scope(即包裹着它的更大的空间)中的变量,这些变量相对于它称之为 nonlocal variable。
Nonlocal variables 是只读的,如果想修改它们,必须使用 nonlocal (非局部的)关键字。
# This is the outer enclosing function (封闭函数)
def print_msg(msg):
# This is the nested function
def printer():
print(msg)
printer()
print_msg("Hello") # We execute the function Output: Hello
We can see that the nested function printer() was able to access the non-local variable msg of the enclosing function.
2、Defining a Closure Function
在上面的例子中,如果外部函数 print_msg() 返回值是其内部函数 printer 呢?
def print_msg(msg):
def printer():
print(msg)
return printer # this got changed
# Now let's try calling this function.
# Output: Hello
another = print_msg("Hello")
another()
我们给 print_msg() 函数传入了参数 “Hello”,该函数返回了一个函数 printer,并赋值给了 another,那么 another 就成了一个函数,可以用 another() 来调用。
此时虽然 print_msg() 已经执行完毕,但再执行 another() 时,“Hello” 这条消息仍然有效,“printer” 这个内部***“变量”*** 也仍然有效,不会因为“离开”了其执行空间而被“销毁”导致无迹可寻。
这种技巧就叫做 Closure。
更进一步:
>>> del print_msg
>>> another()
Hello
>>> print_msg("Hello") # NameError: name 'print_msg' is not defined
即使 print_msg 这个函数体被垃圾回收了,another 以及刚才的内部变量 msg 仍然有效。
3、Closure 的条件
下面三个条件都必须满足,才构成一个 Closure:
- 有一个嵌套函数(nested function)
- nested function 必须引用 nonlocal 变量的值 (refer to a value defined in the enclosing
function.) - The enclosing function must return the nested function. 返回嵌套函数中的内函数
4、Closure 的用处
- Closure 可以减少 global value 的使用,并做到一定程度的 data hiding,提供一个 object
oriented solution(面向对象的解决方案) - 当一个类只包含一个方法,使用 Closure 会更加 elegant(优雅的)。
例如:multiplier 乘法器
def make_multiplier_of(n):
e = 2
def multiplier(x):
return e * x * n
return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
# Output: 54
print(times3(9))
# Output: 30
print(times5(3))
# Output: 120
print(times5(times3(2)))
所有的函数都包含一个 closure 属性,如果该函数是一个 normal function,则该属性为 None;如果该函数是一个 closure function,则该属性为 a tuple of cell objects。
>>> make_multiplier_of.__closure__
>>> times3.__closure__
(<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)
引用了几个 nonlocal 变量就对应几个 cell
>>> times3.__closure__[0].cell_contents # 3
>>> times3.__closure__[1].cell_contents # 2
Python 闭包之经典的 for 循环问题
# 希望一次返回 3 个函数,分别计算 1x1,2x2,3x3
# 可以用列表接收这 3 个函数,然后返回列表。
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # 将 fs 中的三个 f 分别传递给 f1,f2,f3
print(f1(),f2(),f3()) # 9 9 9
print(f1.__closure__)
f1(), f2(), f3() 结果不应该是 1, 4, 9 吗,实际结果为什么都是 9 呢?
原因就是当 count() 函数返回了 3 个函数时,这 3 个函数所引用的变量 i 的值已经变成了 3。由于 f1、f2、f3 并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时,i 已经变为 3 了。
如何修改?
方法一
为变量 i 创建一个封闭的作用域,将 i 传递到封闭的作用域内,即将 i 传递给 j。
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j*j
return g
r = f(i)
fs.append(r)
return fs
方法二
思路比较巧妙,用到了默认参数 j 在函数定义时可以获取到 i 的值,虽然没有用到闭包,但是和方法一有异曲同工之处。
def count():
fs = []
for i in range(1, 4):
def f(j=i): # 已经不是闭包函数了
return j*j
fs.append(f)
return fs