「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」
闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。
举个栗子:
# 计算移动平均值
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
avg = make_averager()
avg(10) # 10.0
avg(11) # 10.5
avg(12) # 11.0
复制代码
在 averager
函数中,series
是自由变量(free variable)。这是一个技术术语,指未在本地作用域中绑定的变量。如果 averager
函数中存在数字、字符串、元组或其他任何不可变类型的变量,并对其进行赋值时,那么这个变量会变成局部变量,之后的调用就会报错。
变量作用域规则
def f1(a):
print(a)
print(b)
f1(3) # 3 NameError: global name 'b' is not defined
b = 6
f1(3) # 3 6
def f2(a):
print(a)
print(b)
b = 9
b = 6
f2(3) # 3 UnboundLocalError: local variable 'b' referenced before assignment
复制代码
上面的代码中,f2
中,Python 编译函数的定义体时,判断 b
是局部变量,因为在函数中给它赋值了。(可以通过生成的字节码( dis(f2)
)证实这种判断),Python 会尝试从本地环境获取 b
。后面调用 f2(3)
时, f2
的定义体会获取并打印局部变量 a
的值,但是尝试获取局部变量 b
的值时,发现 b
没有绑定值。
如果在函数中赋值时想让解释器把 b
当成全局变量,要使用 global
声明,如:
def f3():
global b
print(b)
b = 9
b = 6
f3() # 6
复制代码
nonlocal
声明
nonlocal
声明的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。如果为 nonlocal
声明的变量赋予新值,闭包中保存的绑定也会更新。
举个栗子:
# 计算移动平均值 不保存所有历史
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
复制代码