6

Coming from much less dynamic C++, I have some trouble understanding the behaviour of this Python (2.7) code.

Note: I am aware that this is bad programming style / evil, but I would like to understand it non the less.

vals = [1,2,3]

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

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

This code runs without error, and f manipulates the (seemingly) global list. This is contrary to my prior understanding that global variables that are to be manipulated (and not only read) in a function must be declared as global ....

On the other hand, if I replace vals[0] = 5 with vals += [5,6], execution fails with an UnboundLocalError unless I add a global vals to f. This is what I would have expected to happen in the first case as well.

Could you explain this behaviour?

Why can I manipulate vals in the first case? Why does the second type of manipulation fail while the first does not?

Update: It was remarked in a comment that vals.extend(...) works without global. This adds to my confusion - why is += treated differently from a call to extend?

4

3 回答 3

6

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。我们现在可以看到为什么 using+=不起作用 - 解释器尝试加载v为局部变量,因为它是就地添加的默认行为。因为它不知道是否v是一个列表,所以它本质上假设该行的含义与v = v + [1].

于 2013-12-25T10:29:03.953 回答
2

global当您要分配给外部范围内的变量时需要。如果不使用global,Python 会vals在做赋值时将其视为局部变量。

+=是一个赋值(一个扩充的赋值),vals += [5, 6]相当于读取vals,然后附加[5, 6]到该值并将结果列表分配回原来的vals. 因为vals += [5,6]没有global语句,Python 看到赋值并将vals其视为本地。您没有创建一个名为的局部变量,vals但您尝试将其附加到它并从这里添加UnboundLocalError.

但是对于阅读,没有必要使用global. 该变量将首先在本地查找,然后,如果在本地范围内找不到,则在外部范围内查找,依此类推。由于您正在处理引用类型,因此您在阅读时会返回一个引用。您可以通过该引用更改对象的内容。

这就是为什么.extend()有效(因为它在引用上调用并作用于对象本身)而vals += [5, 6]失败(因为vals既不是 local 也不是marked global)。

这是一个修改后的示例,可以试用(使用本地vals清除UnboundLocalError):

vals = [1, 2, 3]

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

print 'outside', vals
f()
print 'outside', vals
于 2013-12-25T10:07:48.270 回答
0

只要您不更改对象引用,Python 就会保留全局对象。相比

In [114]: vals = [1,2,3]

In [116]: id(vals)
Out[116]: 144255596

In [118]: def func():
    vals[0] = 5
    return id(vals)
   .....: 

In [119]: func()
Out[119]: 144255596

In [120]: def func_update():
    vals = vals
    return id(vals)
   .....: 

In [121]: func_update()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
/homes/markg/<ipython-input-121-f1149c600a85> in <module>()
----> 1 func_update()

/homes/markg/<ipython-input-120-257ba6ff792a> in func_update()
      1 def func_update():
----> 2     vals = vals
      3     return id(vals)

UnboundLocalError: local variable 'vals' referenced before assignment

当您尝试赋值时,Python 将 vals 视为局部变量 - 并且(哎呀)它不存在!

于 2013-12-25T10:13:47.337 回答