为什么分配给多个目标(标识符/属性)会产生奇怪的结果?

时间:2012-12-19 03:23:40

标签: python variable-assignment

我有一些像这样的代码:

def foo():
    bar = initial_bar = Bar()
    while True:
        next_bar = Bar()
        bar.next_bar = next_bar
        bar = next_bar
    return initial_bar

意图是形成Bar s链,可以遵循链表式。

这一切都非常好;但是通过一些被误导的概念,我想用一条线将它剪下来,将循环结束时的赋值复合成一行。

def foo():
    bar = initial_bar = Bar()
    while True:
        next_bar = Bar()
        bar = bar.next_bar = next_bar
    return initial_bar

因为bar = bar.next_bar = next_bar会扩展为bar.next_bar = next_bar,然后会有效bar = bar.next_bar(除非它没有。)

问题是,这不起作用;返回的“初始栏”没有定义next_bar。通过回到更明确的两线解决方案,我可以轻松地解决它,但是发生了什么?

1 个答案:

答案 0 :(得分:5)

是时候退出dis

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (Bar)
              3 CALL_FUNCTION            0
              6 DUP_TOP             
              7 STORE_FAST               0 (bar)
             10 STORE_FAST               1 (initial_bar)

  3          13 SETUP_LOOP              32 (to 48)
        >>   16 LOAD_GLOBAL              1 (True)
             19 POP_JUMP_IF_FALSE       47

  4          22 LOAD_GLOBAL              0 (Bar)
             25 CALL_FUNCTION            0
             28 STORE_FAST               2 (next_bar)

  5          31 LOAD_FAST                2 (next_bar)
             34 DUP_TOP             
             35 STORE_FAST               0 (bar)
             38 LOAD_FAST                0 (bar)
             41 STORE_ATTR               2 (next_bar)
             44 JUMP_ABSOLUTE           16
        >>   47 POP_BLOCK           

  6     >>   48 LOAD_FAST                1 (initial_bar)
             51 RETURN_VALUE        

如果仔细观察,你会看到在关键线(第5行,见左边的数字,位置31-47),它会这样做:

  • 加载next_bar(31)两次(34);
  • 将它(堆栈上的第一个副本)写入bar(35);
  • 将它(堆栈上的第二个副本)写入bar.next_bar(38,41)。

在最小的测试用例中更明显地看到了这一点。

>>> def a():
...     b = c = d
... 
>>> dis.dis(a)
  2           0 LOAD_GLOBAL              0 (d)
              3 DUP_TOP             
              4 STORE_FAST               0 (b)
              7 STORE_FAST               1 (c)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

看看它在做什么。这意味着b = c = d实际上等同于b = d; c = d。通常情况下这并不重要,但在最初提到的情况下,它确实很重要。这意味着在关键线上,

bar = bar.next_bar = next_bar

不等于

bar.next_bar = next_bar
bar = next_bar

而是

bar = next_bar
bar.next_bar = next_bar

事实上,这是在Python文档的第6.2节中记录的Simple statements, Assignment statements

  

赋值语句计算表达式列表(请记住,这可以是单个表达式或以逗号分隔的列表,后者产生元组)并将单个结果对象分配给每个目标列表,从左侧开始正确

该部分还有一个相关的警告适用于这种情况:

  

警告:尽管赋值的定义意味着左侧和右侧之间的重叠是“安全的”(例如a, b = b, a交换两个变量),重叠在分配给变量的集合不安全!例如,以下程序打印[0, 2]

x = [0, 1]
i = 0
i, x[i] = 1, 2
print x

有可能去bar.next_bar = bar = next_bar,这确实产生了最初期望的结果,但是对任何人(包括原作者一段时间之后!)都有同情心,他们将不得不稍后阅读这些代码并为此事感到高兴用他认为蒂姆会用过的话,他会想到这些,

  

明确比一个可能令人困惑的角落更好。