变量作用域
从内层函数的角度看,变量使用的两个维度
- 是否能访问:LEGB 规则
- 是否能修改:需要声明才能修改
变量作用域识别三要素
- 出现位置:在哪里访问了
- 赋值位置:在哪里赋值了
- 声明类型:在哪里声明了
三种变量作用域(声明)
- 局部:
local
(函数调用时才创建) - 全局:
global
(导入模块时创建,直至解释器退出) - 非全局:
nonlocal
划重点:
- 无声明的情况下,赋值即私有,若外部有相同变量名则将其遮挡
- 无赋值情况下,变量访问依据 LEGB 法则
- 内层函数可以通过声明的方式直接修改外部变量
- 位于最内层的函数,通过
global
声明,会越过中间层,直接修改全局变量 -
global
声明其实是一种绑定关系,意思是告诉解释器,不用新创建变量了,我用的是最外面那个 - 位于最内层的函数,如果仅想修改中间层变量,而不是全局变量,可使用
nonlocal
关键字 -
nonlocal
只能绑定在中间层定义的变量,如果中间层变量被声明外全局变量,则会报错
金句:
- 无声明的情况下,赋值即私有,若外部有相同变量名则将其遮挡
- 想修改外部相同变量名,需要将外部变量声明
- 根据外部变量的作用域级别不同,使用
global
或者nonlocal
为什么会有 nonlocal
关键字?
-
nonlocal
填补了global
与local
之间的空白 -
nonlocal
的出现其实是一种权衡利弊的结果:私有之安全封装,全局之灵活共享 -
nonlocal
是 Python 3 中引入的一个官方的解决方案,以弥补内层函数无法修改中间层不可变对象
什么是闭包?
- 定义:延伸了作用域的函数(能访问定义体之外定义的非全局变量)
- 闭包是一种函数,它会保留定义函数时存在的外层非全局变量的绑定
闭包有什么用呢?
- 共享变量的时候避免使用了不安全的全局变量
- 允许将函数与某些数据关联起来,类似于简化版面向对象编程
- 相同代码每次生成的闭包,其延伸的作用域都彼此独立(计数器,注册表)
- 函数的一部分行为在编写时无法预知,需要动态实现,同时又想保持接口一致性
- 较低的内存开销:类的生命周期远大于闭包
- 实现装饰器(装饰器的本质是一个闭包,而
@
仅仅是一个语法糖)
下面我们要实现一个任务:每次调用便计算之前所有的数的平均值。
实现版本1:类实现
class Averager:
def __init__(self):
self.series = []
def __call__(self, new_value):
'''
令类表现的像函数一样,可调用
'''
self.series.append(new_value)
total = sum(self.series)
return total / len(self.series)
avg = Averager()
avg(10), avg(11), avg(21)
(10.0, 10.5, 14.0)
版本2:闭包实现
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), avg(11), avg(21)
(10.0, 10.5, 14.0)
由于每次都需重新计算总数,做了许多不必要的重复,为了简化版本2,我们可以这样:
版本3:优化的闭包实现
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
avg(10), avg(11), avg(21)
(10.0, 10.5, 14.0)
网友评论