从例子讲起
看看和自己预测的结果是否一致,知晓对作用域的了解度
e.g.2:
1 b = 6 2 def f2(a): 3 print(a) 4 print(b) 5 b = 9 6 7 f2(3) 8 9 """ 10 输出: 11 3 12 ........ 13 ........ print(b) 14 UnboundLocalError: local variable ‘b‘ referenced before assignment 15 """
e.g.3:
a = 1 b = 2 def f1(): print(a) print(b) def f2(): a += 1 b += 1 f1() f2()
解析e.g.1:
Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了(第5行:b =9)。
生成的字节码证实了这种判断,Python 会尝试从本地环境获取 b。(稍后解释)
后面调用 f2(3) 时, f2 的定义体会获取并打印局部变量 a 的值,但是尝试获取局部变量 b 的值时,发现 b 没有绑定值。(在函数体中给b=9进行了赋值操作,在复制操作前尝试打印b)
延伸:
这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。这比 JavaScript 的行为好多了,avaScript 也不要求声明变量,
但是如果忘记把变量声明为局变量(使用 var),可能会在不知情的情况下获取全局变量。
字节码验证:
f1函数的字节码(运行字节码的 CPython VM 是栈机器,因此 LOAD 和 POP 操作引用的是栈):
dis 模块为反汇编 Python 函数字节码提供了简单的方式
e.g.1:
def f1(a): print(a) print(b) f1(3)#NameError: name ‘b‘ is not defined 3
此处的结果显而易见,b没有定义
from dis import dis def f1(a): print(a) print(b) dis(f1)
8 0 LOAD_GLOBAL 0 (print) @1 2 LOAD_FAST 0 (a) @2 4 CALL_FUNCTION 1 6 POP_TOP 9 8 LOAD_GLOBAL 0 (print) 10 LOAD_GLOBAL 1 (b) @3 12 CALL_FUNCTION 1 14 POP_TOP 16 LOAD_CONST 0 (None) 18 RETURN_VALUE
@1 加载全局名称 print。
@2加载本地名称 a。
@3加载全局名称 b。
f2函数的字节码
8 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (a) 4 CALL_FUNCTION 1 6 POP_TOP 9 8 LOAD_GLOBAL 0 (print) 10 LOAD_FAST 1 (b) @b 12 CALL_FUNCTION 1 14 POP_TOP 10 16 LOAD_CONST 1 (9) 18 STORE_FAST 1 (b) 20 LOAD_CONST 0 (None) 22 RETURN_VALUE
@b加载本地名称b
这表明,编译器把 b 视作局部变量,即使在后面才为 b 赋值,因为变量的种类(是不是局部变量)不能改变函数的定义体。
为什么在例子e.g.3中f1中可以输出a,b;但是在f2中却不能输出a,b :
因为f1中函数体中的a,b是*变量(free variable):因为在函数体中没有对其赋值操作( a += 1; a = a+1),只是调用了a,b的值。
而f2中的值对a,b进行了赋值操作,隐式的创建了局部变量a,b,此时创建的局部变量并没有事先被定义:声明全局变量关键字global
声明*变量关键字:nonlocal(在嵌套函数中使用)
Python中的LENGB规则:
L(local):函数内的变量
E(engclosing function locals):嵌套作用域
N(nonlocal) :只作用于嵌套作用域,而且只是作用在函数里面(一般用于闭包)
G(Global):函数定义所在模块的变量
B(Builtin):Python内建函数的名字空间
在Python程序中声明、改变、查找变量名时,都是在一个保存变量名的命名空间中进行中,此命名空间亦称为变量的作用域。
python的作用域是静态的,在代码中变量名被赋值的位置决定了该变量能被访问的范围。
即Python变量的作用域由变量所在源代码中的位置决定.变量作用域之LENGB
python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量.
声明全局变量时:先用global关键字声明这个变量,然后再给这个变量赋值 不可直接写成 global variable = 1;
而应 global x; variable =1
e.g.3.modify:
a = 1 b = 2 def f1(): print(a) print(b) def f2(): global a, b a += 1 b += 1 print(a, b) f1() f2() """ 1 2 2 3 """
e.g.2.modify:
b = 6
def f2(a):
global b
print(a, end=‘ ‘)
print(b, end=‘ ‘)
b = 9
f2(3)
print(b, end=‘ ‘)
b = 30
f2(3)
print(b, end=‘ ‘)
#3 6 9 3 30 9
nonlocal
用法:这个一般是用在闭包函数里. 但是一定要认识到, nonlocal声明的变量在上级局部作用域内,而不是全局定义, 如果在它声明的变量在上级局部中不存在,则会报错. 实例如下:
e.g.4:
def outer(): x = 100 def inner(): x = 200 inner() print(x) outer()#100
那如果要求inner中对x的修改是有效的,必须加上关键字nonlocal
e.g.5:
nonlocal它的作用是把变量标记为*变量,即使在函数中为变量赋予新值了,也会变成*变量。如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。(需要理解闭包的概念)
def outer(): x = 100 def inner(): nonlocal x x = 200 inner() print(x) outer()#200
e.g.6:
def outer(): # x = 100 #注释掉 nonlocal上层局部变量 def inner(): nonlocal x x = 200 inner() print(x) outer()#SyntaxError: no binding for nonlocal ‘x‘ found
理解nonlocal上层,局部变量:一下代码仍然会报错
x =100 def outer(): # x = 100 def inner(): nonlocal x x = 200 inner() print(x) outer()#SyntaxError: no binding for nonlocal ‘x‘ found
闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量
关键是它能访问定义体之外定义的非全局变量。
看下面的例子:
计算移动平均值的类:
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) if __name__ == ‘__main__‘: avg = Averager() print(avg(10))#10.0 print(avg(11))#10.5 print(avg(12))#11.0
以上是类的实现,
下面来看一下高阶函数的实现:
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager if __name__ == ‘__main__‘: avg = make_averager() print(avg(10))#10.0 print(avg(11))#10.5 print(avg(12))#11.0
注意,这两个示例有共通之处:调用 Averager() 或make_averager() 得到一个可调用对象 avg,它会更新历史值,然后计算当前均值。在类中,avg 是 Averager 的实例;
在高阶函数中avg 是内部函数averager。不管怎样,我们都只需调用 avg(n),把 n放入系列值中,然后重新计算均值。
以类实现的avg实例在哪里存储历史值:很明显:self.series实例属性,那么在高阶函数中,avg对象在哪里寻找series呢?
注意,series 是 make_averager 函数的局部变量,因为那个函数的定义体中初始化了 series:series = []。可是,调用 avg(10)时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了。
在 averager 函数中,series 是*变量(free variable)。这是一个技术术语,指未在本地作用域中绑定的变量
averager 的闭包延伸到那个函数的作用域之外,包含*变量 series 的绑定
综上,闭包是一种函数,它会保留定义函数时存在的*变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中
的外部变量。
参考:
Python进阶