在Python中写入(而非写入)全局变量


问题内容

来自不太动态的C ++,我在理解此Python(2.7)代码的行为时遇到了一些麻烦。

注意: 我知道这是不好的编程风格/邪恶,但我希望对此有所了解。

vals = [1,2,3]

def f():
    vals[0] = 5
    print 'inside', vals

print 'outside', vals
f()
print 'outside', vals

此代码运行无误,并f操纵(看似)全局列表。这与我先前的理解相反,必须将要在函数中操作(且不仅要读取)的全局变量声明为global ...

另一方面,如果我替换vals[0] = 5vals += [5,6],则UnboundLocalError除非添加global vals到,否则执行将失败并显示为f。这也是我在第一种情况下的预期。

您能解释一下这种行为吗?

为什么vals在第一种情况下可以操作?为什么第二种类型的操作会失败而第一种类型的却不会失败?

更新:vals.extend(...)没有的情况下,评论中指出了这一点global。这加剧了我的困惑-
为什么+=将呼叫与区别对待extend


问题答案:

global仅在尝试更改变量引用的对象时才需要。因为vals[0] = 5更改的是实际对象而不是参考,所以不会引发任何错误。但是,使用vals += [5, 6],解释器会尝试查找局部变量,因为它无法更改全局变量。

令人困惑的是,将+=运算符与list一起使用会修改原始列表,例如vals[0] = 5。虽然vals += [5, 6]失败了,但vals.extend([5, 6])行得通。我们可以争取到的帮助为dis.dis我们提供一些线索。

>>> def a(): v[0] = 1
>>> def b(): v += [1]
>>> def c(): v.extend([1])
>>> import dis
>>> dis.dis(a)
  1           0 LOAD_CONST               1 (1)
              3 LOAD_GLOBAL              0 (v)
              6 LOAD_CONST               2 (0)
              9 STORE_SUBSCR        
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
>>> dis.dis(b)
  1           0 LOAD_FAST                0 (v)
              3 LOAD_CONST               1 (1)
              6 BUILD_LIST               1
              9 INPLACE_ADD         
             10 STORE_FAST               0 (v)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        
d
>>> dis.dis(c)
  1           0 LOAD_GLOBAL              0 (v)
              3 LOAD_ATTR                1 (extend)
              6 LOAD_CONST               1 (1)
              9 BUILD_LIST               1
             12 CALL_FUNCTION            1
             15 POP_TOP             
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

我们可以看到功能ac用途LOAD_GLOBAL,而b尝试使用LOAD_FAST。现在我们可以看到为什么使用+=不起作用的原因-
解释器尝试将其v作为局部变量加载,因为它是就地添加的默认行为。由于它不知道是否v为列表,因此基本上假设该行的含义与相同v = v + [1]