【问题标题】:Writing (and not) to global variable in Python在 Python 中写入(而不是)到全局变量
【发布时间】:2013-12-25 09:59:59
【问题描述】:

来自不太动态的 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 的调用不同?

【问题讨论】:

  • 我不是很喜欢python,但它是否允许在没有“全局”声明的情况下读取全局变量?我认为解释可能是分配给vals[0] 没有assignvals(它仍然是同一个数组),所以这样做vals[0]=5 是一种阅读 i>.
  • 我认为这与更改变量引用的对象有关。我不明白为什么vals.extend([5, 6]) 可以工作,而vals += [5, 6] 不行,因为两者在列表方面基本相同。
  • @AntonKovalenko 它确实允许在没有全局语句的情况下阅读。来自一个 const 正确性的世界,让我感到困惑的是,两个改变值的操作似乎被完全不同地对待。
  • @Volatility:太棒了!我没试过。增加了我的困惑。

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


【解决方案1】:

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] 相同。

【讨论】:

  • 很好的答案!在 C++ 中,将 += 视为复制加加会产生严重的性能影响。它在Python中吗?扩展更快吗?
  • @DCS 什么是“复制加添加”?在 Python 中,. 运算符会带来一些开销,因此 extend 会稍微慢一些。 +=extend 几乎相同,但当你没有重复一百万次时。
  • 明白。在 C++ 中,如果不小心使用,向量 (=list) 等类上的 A=A+C 很容易触发不必要的 A 重复。
【解决方案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

【讨论】:

  • 我明白你的意思,操作列表元素不是一项任务。用户波动性评论说,在列表上调用 extend 确实可以在没有全局语句的情况下工作——我知道这也不是一个赋值。但是为什么 += 被视为赋值而 extend 不是呢?
  • @DCS: extend() 是一种在您从读取中返回的引用上调用的方法。它附加到现有对象。相反,+= 是一个分配,实际上是 augmented assignmentvals += [5,6] 加载 vals,然后将 [5, 6] 添加到其中,并将结果对象存储回原始 vals。问题就在这里。在这个分配给vals 的任务中,它没有被声明为global,而且你也没有一个本地的。
  • 所以,如果我理解正确的话, SomeList+=Val 实际上被视为 SomeList = SomeList+Val 而不是对扩展方法的覆盖?
  • @DCS 基本上,是的。至少就语法而言(它实际上可能会在稍后阶段优化为blah.extend...,但那是在引发错误之后)。
【解决方案3】:

只要不更改对象引用,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 视为局部变量 - 并且(哎呀)它不存在!

【讨论】:

    猜你喜欢
    • 2019-07-24
    • 1970-01-01
    • 1970-01-01
    • 2017-12-20
    • 2014-12-19
    • 1970-01-01
    • 1970-01-01
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多