闭包
(closure)作为一个不太容易了解的概念呈现在很多支流编程语言中,Python 中很多高级实现都离不开 闭包
, 装璜器
就是应用 闭包
的典型例子。
作用域
要学习 闭包
,先理解 作用域
的概念。
作用域
是程序运行时变量的存在范畴。常见 作用域
有 全局作用域
和 部分作用域
,定义在 全局作用域
中的变量,在程序运行过程中的任何中央都能够拜访到;而定义在函数外部的变量只有在函数外部才可能拜访,函数外部的作用域就是 部分作用域
,为了便于了解,我这里称它为 函数作用域
。
全局作用域
不能够读取 函数作用域
中的局部变量:
def foo():
num = 100
print(num) # NameError: name 'num' is not defined
函数作用域
能够向上读取 全局作用域
中的全局变量:
num = 100
def foo():
print(num) # 100
foo()
闭包
在 Python 中,函数是 一等公民
,意思是说你能够像对待 int
、dict
等常见的数据类型一样去对待它。其实函数不仅仅能够定义在 全局作用域
中,函数也能够定义在另一个 函数作用域
中。
def foo():
num = 100
def bar():
print(num) # 100
bar()
foo()
以上代码中,在 foo
函数外部又定义了一个 bar
函数。bar
函数能够向上读取 foo
函数内定义的任何变量。bar
函数读取变量 num
的程序是:先在 bar
函数本人的 函数作用域
内查找,如果找不到就向上查找 foo
函数的 作用域
,如果还是找不到,最初再向上查找 全局作用域
。
以上代码稍作批改,就能成为一个 闭包
函数:
def foo():
num = 100
def bar():
print(num) # 100
return bar
result = foo()
result()
能够看到,只须要将之前的示例代码中 bar()
这句函数调用,改为 return bar
,而后在调用 foo
函数时用一个变量 result
接管 foo
函数的返回值,最初再调用 result
这个变量,就能失去同样的后果。这样其实间接的的实现了在 全局作用域
中 读取函数作用域
中的变量。这就是 闭包
的威力。
像这样,函数嵌套函数,外部函数援用内部函数 作用域
中的局部变量,内部函数将外部函数的援用当作返回值返回,此时,咱们就能够将外部函数和它援用的内部函数 作用域
中的局部变量统称为 闭包
。
闭包的利用
如果须要实现一个计数器,咱们很容易想到应用 类
来实现:
class Counter(object):
def __init__(self):
self.num = 0
def __call__(self):
self.num += 1
return self.num
counter = Counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
因为 类
能够保留数据并且操作数据,所以很轻松就可能应用 类
来实现计数器。
而函数自身没法在每次调用时保留数据,所以无奈实现一个计数器的性能。但当咱们有了 闭包
函数,就可能用 函数
的模式来实现计数器了。
def make_counter():
num = 0
def counter():
nonlocal num
num += 1
return num
return counter
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
以上,咱们就用 闭包函数
实现了一个计数器。
能够看到外部 counter
函数有一条语句 nonlocal num
,关键字 nonlocal
的作用能够对应 global
关键字来了解。当咱们在 函数作用域
外部批改 全局作用域
中的 不可变类型
变量时,咱们会应用 global
关键字来表明一个变量是全局变量,同样的 nonlocal
关键字的作用是表明 num
是一个 闭包
中的变量,它有一个业余的术语叫做 自在变量
。通常来说,一个函数执行实现当前,其外部的变量会随之被销毁,而 自在变量
num
并不会被立刻销毁,它伴随 counter
函数一起组成了 闭包
。
闭包的陷阱
上面是一个应用 闭包
时常见的误区:
def func():
li = []
for i in range(3):
def f():
return i
li.append(f)
return li
li = func()
for f in li:
print(f())
# 运行后果
# 2
# 2
# 2
咱们期待的打印后果顺次为 0
、1
、2
,但实际上运行以上代码,失去的后果确是 2
、2
、2
。
这是因为,外部函数 f
和循环变量 i
造成了 闭包
。func
函数外部共产生了 3 个 f
函数,它们顺次被追加到 li
列表中,然而并没有立刻被执行,而其外部援用了 自在变量
i
,在 3 次 for
循环执行实现后,i
的值曾经变成了 2
,等到在函数内部接管到 li
再一次循环执行每个 f
函数时,所打印的 i
的值天然就都为 2
了。
要解决这个问题,须要想方法在每次循环时,让外部的 闭包
函数 f
绑定住以后的循环变量,而不使其追随 i
的变动而扭转:
def func():
li = []
for i in range(3):
def wrap(j):
def f():
return j
return f
li.append(wrap(i))
return li
li = func()
for f in li:
print(f())
# 运行后果
# 0
# 1
# 2
在这里,我在外部函数 f
的内部又嵌套了一层函数 wrap
,在执行 li.append(wrap(i))
语句时,将循环变量 i
的值传递给 wrap
函数,这样 wrap
函数执行实现后,函数 f
和变量 j
就组成了 闭包
。每次 for
循环时,都将循环变量 i
当作参数传递给 wrap
函数,因为每产生的 wrap
函数互相独立并且绑定到函数参数上的变量值不会扭转,这样就能保障每次循环过程中在 wrap
函数外部保留的变量 j
相互独立,所以最终可能失去预期的后果。
首发地址:https://jianghushinian.cn/