全局字典不需要使用关键字global来修改它们吗?

51
我想知道为什么我可以在没有使用global关键字的情况下更改全局字典? 为其他类型强制使用这个关键字的原因是什么? 这背后有何逻辑?
例如代码:
#!/usr/bin/env python3

stringvar = "mod"
dictvar = {'key1': 1,
           'key2': 2}

def foo():
    dictvar['key1'] += 1

def bar():
    stringvar = "bar"
    print(stringvar)

print(dictvar)
foo()
print(dictvar)

print(stringvar)
bar()
print(stringvar)

给出以下结果:
me@pc:~/$ ./globalDict.py 
{'key2': 2, 'key1': 1}
{'key2': 2, 'key1': 2}  # Dictionary value has been changed
mod
bar
mod

我期望的是:

me@pc:~/$ ./globalDict.py 
{'key2': 2, 'key1': 1}
{'key2': 2, 'key1': 1}  # I didn't use global, so dictionary remains the same
mod
bar
mod

我看过 SO: Python 中的 Global 关键字SO: 为什么关键字 global 不是必须的,因此我知道在没有global的情况下字典是如何工作的,但我仍然不明白为什么Python根据变量类型有不同的范围访问方式? - Jovik
4
你还没有理解 Python 中可变和不可变对象的区别。如果你阅读 http://docs.python.org/2/reference/datamodel.html(至少是其 3.1 小节,不要跳过其中任何部分,因为你认为自己已经知道了),那么这个区别应该会变得清晰明了。 - mmgp
1
这个问题与Python 3无关。 - Martijn Pieters
2个回答

57

原因是该行

stringvar = "bar"

这段代码含义不明确,它可能指代一个全局变量,或者它可能在创建一个名为stringvar的新局部变量。在这种情况下,Python默认将其视为局部变量,除非已经使用了global关键字。

然而,这行代码

dictvar['key1'] += 1

这句话非常明确。它只能指代全局变量dictvar,因为dictvar必须已经存在,否则该语句会抛出错误。

这不仅限于字典- 对于列表也是如此:

listvar = ["hello", "world"]

def listfoo():
    listvar[0] = "goodbye"

或其他类型的对象:

class MyClass:
    foo = 1
myclassvar = MyClass()

def myclassfoo():
    myclassvar.foo = 2

只要使用了 变异操作而不是重新绑定操作,这就是真的。


如果你考虑的不是Python语言,那么stringvar = "bar"才会有歧义。这个答案只有底部的链接可能提供一点点帮助。 - mmgp
10
@mmgp:我觉得你误解了我的意思。我并不是说这种行为是模棱两可或未定义的。OP问为什么Python会表现出这样的行为,我解释了它为什么没有选择——因为它无法知道当你有一行stringvar = "bar"时,你是想引用全局变量还是创建一个局部变量。因此,它默认创建一个局部变量。 - David Robinson
1
那么,如果有一个名为dictvar的本地字典,它会在全局字典之前被调用吗? - Jin
1
@Jin 是的,没错! - David Robinson

13

您可以在不使用global关键字的情况下修改任何可变对象。

这在Python中是可能的,因为当您想要将新对象重新分配给已在全局作用域中使用的变量名称或定义新的全局变量时,会使用global关键字。

但是对于可变对象,您并没有重新分配任何内容,而是直接在原地修改它们,因此Python只需从全局作用域加载它们并进行修改。

正如文档所说:

如果没有使用global,则无法分配全局变量。

In [101]: dic = {}

In [102]: lis = []

In [103]: def func():
    dic['a'] = 'foo'
    lis.append('foo') # but  fails for lis += ['something']
   .....:     

In [104]: func()

In [105]: dic, lis
Out[105]: ({'a': 'foo'}, ['foo'])

dis.dis

In [121]: dis.dis(func)
  2           0 LOAD_CONST               1 ('foo')
              3 LOAD_GLOBAL              0 (dic)     # the global object dic is loaded
              6 LOAD_CONST               2 ('a')
              9 STORE_SUBSCR                         # modify the same object

  3          10 LOAD_GLOBAL              1 (lis)    # the global object lis is loaded
             13 LOAD_ATTR                2 (append)
             16 LOAD_CONST               1 ('foo')
             19 CALL_FUNCTION            1
             22 POP_TOP             
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE  

我理解我可以做到(以及其背后的机制)。让我困扰的是为什么Python会为global语句做出异常处理? - Jovik
1
@Jovik:没有异常被引发。你将变量赋值和值操作搞混了。你不能执行 var = 'string'var.insert(0, 'othertext') 因为字符串是不可变的,你只能替换变量指向的值。但是你可以更改一个可变变量。你可以执行 var1 = {}var2 = var1var2.update({'another': 'dict'}),然后你会看到 var1 反映了同样的变化。你改变了一个可变值,而不是指向它的变量。 - Martijn Pieters

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