在Python中编写(而不是)全局变量

时间:2013-12-25 10:00:00

标签: python python-2.7 global-variables python-internals

来自动态性较差的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] = 5替换为vals += [5,6],则执行会因UnboundLocalError而失败,除非我向global vals添加f。这也是我在第一种情况下预期会发生的事情。

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

为什么我可以在第一种情况下操纵vals?为什么第二种操作失败而第一种操作失败?

更新 评论中注明vals.extend(...)在没有global的情况下有效。这增加了我的困惑 - 为什么+=extend的调用区别对待?

3 个答案:

答案 0 :(得分:5)

只有在尝试更改变量引用的对象时才需要

global。因为vals[0] = 5更改实际对象而不是引用,所以不会引发错误。但是,对于vals += [5, 6],解释器会尝试查找局部变量,因为它无法更改全局变量。

令人困惑的是,使用带有列表的+=运算符会修改原始列表,例如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]相同。

答案 1 :(得分:2)

如果要分配外部作用域中的变量,则需要

global。如果您不使用global,Python在执行赋值时会将vals视为局部变量。

+=是一项作业(augmented assignment),vals += [5, 6]相当于阅读vals,然后将[5, 6]附加到该值并指定结果列表回到原来的vals。由于vals += [5,6]没有global语句,因此Python会查看分配并将vals视为本地语句。您没有创建名为vals的局部变量,但是您尝试将其附加到UnboundLocalError

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

这就是.extend()工作的原因(因为它在引用上调用并作用于对象本身)vals += [5, 6]失败(因为vals既不是本地也不是global) 。

以下是试用的修改示例(使用本地vals清除UnboundLocalError):

vals = [1, 2, 3]

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

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

答案 2 :(得分: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是局部变量 - 而且(oops)它不存在!