Python变量作用域的困惑

7
我发现了一些让我感到困惑的代码。下面是一个最小化的示例来展示这个问题:
# of course, the ... are not part of the actual code
some_var = {"key1":"value1" ... "keyN":"valueN"}

def some_func():
   v = some_var["key1"]

代码可以正常运行,但我能够直接访问some_var这一事实令我感到困惑。上一次我写Python代码时,我记得必须像这样编写some_func:
def some_func():
   global some_var
   v = some_var["key1"]

我正在使用Windows 7电脑上的Python 2.7.1版本。2.7版本中是否有任何更改,可以实现这一点?

6个回答

6
不,你不能在局部作用域中重新分配 some_var。考虑以下示例:
some_var = {}
def some_func():
    # some_var[5] = 6
    some_var = {1:2}
    some_var[3] = 4
some_func()
print (repr(some_var)) # {}

您将会在some_func中看到一个赋值语句,它实际上创建了一个局部变量来遮盖全局变量。因此,取消注释该行代码将导致UnboundLocalError错误-您不能在定义之前访问变量。


2
这是我不喜欢 Python 的(极少数)事情之一。 - Peter C
@Geo:因为赋值和访问是两回事。 - user225312
@Geo 这是因为你可以访问(而不是分配)外部作用域的任何值。这就是Python中作用域的工作方式,如果不是这样,你将不得不在函数内部使用每个变量和函数时都加上 global 关键字。 - phihag
@Geo:您可以在比当前范围更大的范围内引用变量。但是你不能对它们赋值。在这种情况下,您需要使用global关键字。 - Peter C
@alpha:这可能看起来很奇怪,但由于您创建新的本地变量的频率比覆盖非本地变量的频率要高得多,所以这实际上是一个不错的选择。当然,假设存在“nonlocal”(否则闭包可能会变得非常丑陋)。诚然,警告会很好。但再次,动态性并不容易做到这一点。 - user395760
显示剩余3条评论

3

只有在想要给该变量分配新值时,才需要使用global

Python 2.1引入了嵌套作用域(并且在Python 2.2中默认启用)(强调是我的):

简单地说,如果一个给定的变量名在函数内部没有被赋值(通过赋值、defclassimport语句),则对该变量的引用将在封闭作用域的本地命名空间中查找。更详细的规则说明和实现分析可以在PEP中找到。


啊,所以只有重新分配变量需要使用 global。其他所有操作都可以进行? - Geo
@Geo 不可以使用 del 删除变量。基本上,你只能读取它们 - 但这包括 Python 中几乎所有的东西,包括数组访问、调用方法、在对象上调用方法等等。 - phihag

3

在使用(例如调用或在表达式中使用)外部作用域的名称和分配它之间存在区别(而且在将变量赋值给对象的成员时,如x.y = ...x[...] = ...被视为方法调用!)。

如果你要重新分配变量,则只需要声明变量来自于外部作用域。在Python 2中,您只能通过global var来实现这一点,在Python 3中,您可以在任意嵌套作用域(例如闭包)中使用nonlocal var

在您的示例中使用非本地变量不需要global。但是,一旦您在该作用域内的任何位置分配变量(根据前面提到的分配定义),则需要global - 因此,在使用变量的行后添加some_var = ...一行,您将获得一个UnboundLocalError。有关详细信息,请参阅文档。


2
只有在你想要给变量赋值时才需要使用global,当读取变量时不需要这样做。虽然一开始看起来可能没有什么规律,但这种区别并不是任意的。
当读取一个值时,解释器只需查找名为some_var的局部变量,如果找不到,则会查找同名的全局变量。这些是简单而直接的语义。
当将值分配给变量时,解释器需要知道您是要将其分配给局部变量some_var还是全局变量。 当在函数内部调用some_var = 2时,解释器会认为您正在将其分配给局部变量,这是最常见的情况,这是有道理的。对于相对较少的情况,即您想从函数内部分配给全局变量时,可以使用全局修饰符global some_var = 2

1
将一个值赋给一个名称会使该名称成为局部变量,除非该名称被明确声明为全局变量。
a = 12

def foo():
   a = 42
   print a   # uses local

foo()
>>> 42

def foo():
   global a
   a = 42

foo()
print a
>>> 42

如果一个名称没有被分配,它就是全局的。
a = 12

def foo():
   print a   # uses global

foo()
>>> 12

简而言之,只有在你需要对变量进行赋值时才需要显式地声明它为全局变量。如果你只是从中读取数据,可以随意使用它。但是,如果你曾经对该变量进行过赋值,在函数内部它将被视为局部变量,除非你声明它为全局变量。
b = 5

def foo():
   print b
   b = 7

foo()
>>> ???

由于在foo()中给b赋值但没有声明为全局变量,Python在编译时决定b是一个本地名称。因此,在整个函数中,包括赋值之前的print语句,b都是一个本地名称。

因此,print语句会报错,因为本地名称b尚未定义!


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接