熟悉的常见首字母缩写词是LEGB:
这是 Python 搜索命名空间以查找变量分配的顺序。
本地
本地命名空间是当前代码块中发生的一切。函数定义包含局部变量,这是 Python 查找变量引用时首先找到的东西。在这里,Python 将首先在foo 的本地范围内查找,找到分配了2 的x 并打印出来。尽管x 也在全局命名空间中定义,但所有这些都会发生。
x = 1
def foo():
x = 2
print(x)
foo()
# prints:
2
当 Python 编译一个函数时,它决定定义代码块中的每个变量是局部变量还是全局变量。为什么这很重要?让我们看一下foo 的相同定义,但翻转其中的两行。结果可能令人惊讶
x = 1
def foo():
print(x)
x = 2
foo()
# raises:
UnboundLocalError: local variable 'x' referenced before assignment
出现此错误的原因是,由于分配了x = 2,Python 将x 编译为foo 内的local 变量。
您需要记住的是,局部变量只能访问其自身范围内的内容。
封闭
定义多层函数时,未编译为本地的变量将在下一个最高命名空间中搜索它们的值。这是一个简单的例子。
x = 0
def outer_0():
x = 1
def outer_1():
def inner():
print(x)
inner()
outer_1()
outer_0()
# print:
1
当inner() 被编译时,Python 将x 设置为全局变量,这意味着它将尝试访问本地范围之外的x 的其他赋值。 Python 在封闭的命名空间中向上搜索 x 值的顺序。 x 不包含在outer_1 的命名空间中,因此它检查outer_0,找到一个值并将该分配用于inner 中的x。
x --> inner --> outer_1 --> outer_0 [ --> global, not reached in this example]
您可以使用关键字nonlocal 和global 强制变量不是本地变量(注意:nonlocal 仅在 Python 3 中可用)。这些是编译器关于变量范围的指令。
nonlocal
使用nonlocal 关键字告诉python 将变量分配给在命名空间向上移动时找到的第一个实例。对变量所做的任何更改也将在变量的原始命名空间中进行。在下面的例子中,当2被分配x时,它也是在outer_0的范围内设置x的值。
x = 0
def outer_0():
x = 1
def outer_1():
def inner():
nonlocal x
print('inner :', x)
x = 2
inner()
outer_1()
print('outer_0:', x)
outer_0()
# prints:
inner : 1
outer_0: 2
全球
全局命名空间是您程序运行的最高级别命名空间。它也是所有函数定义的最高封闭命名空间。一般来说,将值传入和传出全局命名空间中的变量并不是一个好习惯,因为可能会发生意外的副作用。
global
使用global 关键字类似于非本地关键字,但不是向上通过命名空间层,而是仅在全局命名空间中搜索变量参考。使用上面的相同示例,但在这种情况下,声明 global x 告诉 Python 在全局命名空间中使用 x 的分配。这里全局命名空间有x = 0:
x = 0
def outer_0():
x = 1
def outer_1():
def inner():
global x
print('inner :', x)
inner()
outer_1()
outer_0()
# prints:
0
同样,如果一个变量还没有在全局命名空间中定义,它会引发错误。
def foo():
z = 1
def bar():
global z
print(z)
bar()
foo()
# raises:
NameError: name 'z' is not defined
内置
最后,Python 会检查内置关键字。诸如 list 和 int 之类的原生 Python 函数是 Python 检查 AFTER 检查变量的最终参考。您可以重载原生 Python 函数(但请不要这样做,这是个坏主意)。
这是一个你不应该做的事情的例子。在dumb 中,我们通过在dumb 的范围内将其分配给0 来重载本机Python list 函数。在even_dumber 中,当我们尝试使用list 将字符串拆分为字母列表时,Python 将在dumb 的封闭命名空间中找到对list 的引用并尝试使用它,从而引发错误。
def dumb():
list = 0
def even_dumber():
x = list('abc')
print(x)
even_dumber()
dumb()
# raises:
TypeError: 'int' object is not callable
您可以通过引用list 的全局定义来恢复原始行为:
def dumb():
list = [1]
def even_dumber():
global list
x = list('abc')
print(x)
even_dumber()
dumb()
# returns:
['a', 'b', 'c']
但同样,不要这样做,这是不好的编码习惯。
我希望这有助于揭示命名空间在 Python 中的一些工作方式。如果您想了解更多信息,Luciano Ramalho 撰写的 Fluent Python 第 7 章对 Python 中的命名空间和闭包进行了精彩的深入演练。