我对下面的代码片段中的全局关键字行为感到困惑,我在所有3个版画中都期待30,30,30。
def outer_function():
#global a ###commented intentionally
a = 20
def inner_function():
global a
a = 30
print('a =',a)
inner_function()
print('a =',a)
a = 10
outer_function()
print('a =',a)
#Output:
#30
#20 #Expecting 30 here
#30
所有混淆来自外部函数定义后的“全局a”。正如我在这个时间点的理解是“所有对变量的引用和赋值都全局反映在该变量上的全局关键字声明中”。如果我没有注释到第一个全球状态网,那么我预计会出现这种情况。请参阅30,30,30。
为什么inner_function和value change中的全局声明不会反映在第二个打印i:e到outer_function(或外部作用域),而是反映在全局命名空间中。
请帮我澄清一下我的困惑。 (对于那里的一些蟒蛇人来说,我可能听起来很愚蠢,但我不希望/在这里有任何downvote)
答案 0 :(得分:5)
熟悉的常见首字母缩写是 LEGB :
这是Python搜索命名空间以查找变量赋值的顺序。
本地命名空间是当前代码块中发生的所有事情。函数定义包含局部变量,这是Python查找变量引用时首先发现的变量。在这里,Python首先查看foo
的本地范围,找到x
并分配2
并打印出来。尽管在全局命名空间中也定义了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
的分配,Python在foo
内将x = 2
编译为本地变量。
您需要记住的是,局部变量只能访问自己范围内的内容。
定义多层函数时,未编译为本地的变量将在下一个最高命名空间中搜索它们的值。这是一个简单的例子。
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
,查找值并将该分配用于x
内的inner
。
x --> inner --> outer_1 --> outer_0 [ --> global, not reached in this example]
您可以使用关键字nonlocal
和global
强制变量不是本地变量(注意:nonlocal
仅适用于Python 3)。这些是编译器关于变量范围的指令。
nonlocal
使用nonlocal
关键字告诉python将变量分配给在向上移动通过命名空间时找到的第一个实例。对变量所做的任何更改也将在变量的原始命名空间中进行。在下面的示例中,当2
被分配x
时,它也会在x
的范围内设置outer_0
的值。
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函数是 AFTER 检查变量的最终参考Python检查。你可以重载本机Python函数(但请不要这样做,这是一个坏主意)。
这是你不应该做的事情的一个例子。在dumb
中,我们通过将本地Python list
函数分配到0
范围内的dumb
来重载该函数。在even_dumber
中,当我们尝试使用list
将字符串拆分为字母列表时,Python会在list
的封闭命名空间中找到dumb
的引用并尝试使用它,引发错误。
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中的命名空间和闭包。
答案 1 :(得分:2)
全球声明是一个适用于整个声明的声明 当前代码块。这意味着列出的标识符是 被解释为全局。
请注意,它仅适用于当前代码块。因此global
中的inner_function
仅适用于inner_function
。除此之外,标识符不是全局的。
请注意“标识符”与“变量”的不同之处。所以它告诉解释器“当我在这个代码块中使用标识符a
时,不应用正常的范围解析,我实际上是指模块级变量,”。
答案 2 :(得分:1)
只需取消注释outer_function中的全局命令,否则您将声明一个值为20的局部变量,更改全局变量,然后打印相同的局部变量。
答案 3 :(得分:1)
使用全局变量并不是一个好主意。如果只想重置变量的值,只需使用以下行:
def outer_function():
a = 20
def inner_function():
a = 30
print('a =',a)
return a
a = inner_function()
print('a =',a)
return a
a = 10
a = outer_function()
print('a =',a)