为什么分配给多个目标(标识符/属性)会产生奇怪的结果? [英] Why is assigning to multiple targets (identifier/attribute) producing strange results?

查看:28
本文介绍了为什么分配给多个目标(标识符/属性)会产生奇怪的结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些这样的代码:

def foo():bar = initial_bar = Bar()为真:next_bar = Bar()bar.next_bar = next_barbar = next_bar返回 initial_bar

目的是形成一个Bar链,可以遵循,链表样式.

这一切都很好;但由于一些误导性的想法,我想将其减少一行,将循环末尾的分配组合成一行.

def foo():bar = initial_bar = Bar()为真:next_bar = Bar()bar = bar.next_bar = next_bar返回 initial_bar

因为 bar = bar.next_bar = next_bar 将扩展为 bar.next_bar = next_bar 后跟有效的 bar = bar.next_bar.(除非它没有.)

问题是,这行不通;返回的初始栏"没有定义它的 next_bar.通过返回到更明确的两行解决方案,我可以很容易地解决这个问题,但是发生了什么?

解决方案

是时候退出 dis.

<预><代码>>>>导入文件>>>dis.dis(foo)2 0 LOAD_GLOBAL 0(条形)3 CALL_FUNCTION 06 DUP_TOP7 STORE_FAST 0(条形)10 STORE_FAST 1 (initial_bar)3 13 SETUP_LOOP 32(到 48)>>16 LOAD_GLOBAL 1(真)19 POP_JUMP_IF_FALSE 474 22 LOAD_GLOBAL 0(条形)25 CALL_FUNCTION 028 STORE_FAST 2 (next_bar)5 31 LOAD_FAST 2 (next_bar)34 DUP_TOP35 STORE_FAST 0 (bar)38 LOAD_FAST 0 (bar)41 STORE_ATTR 2 (next_bar)44 绝对跳跃 16>>47 POP_BLOCK6 >>48 LOAD_FAST 1 (initial_bar)51 RETURN_VALUE

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

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

这在最小测试用例中更明显.

<预><代码>>>>定义一个():... b = c = d...>>>dis.dis(a)2 0 LOAD_GLOBAL 0 (d)3 DUP_TOP4 STORE_FAST 0 (b)7 STORE_FAST 1 (c)10 LOAD_CONST 0(无)13 RETURN_VALUE

看看它在做什么.这意味着 b = c = d 实际上等价于 b = d;c = d.通常这无关紧要,但在最初提到的情况下,这确实很重要.这意味着在临界线上,

bar = bar.next_bar = next_bar

不等于

bar.next_bar = next_barbar = next_bar

而是要

bar = next_barbar.next_bar = next_bar

事实上,这在 Python 文档的第 6.2 节中有记录,简单语句赋值语句:

<块引用>

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

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

<块引用>

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

x = [0, 1]我 = 0i, x[i] = 1, 2打印 x

可以使用 bar.next_bar = bar = next_bar 并且这确实会产生最初想要的结果,但对任何人(包括一段时间后的原作者!)都感到遗憾,他们将不得不稍后阅读代码并为以下事实感到高兴,我相信 Tim 会使用这些词,如果他想到它们,

<块引用>

显式比可能令人困惑的极端情况要好.

I have some code like this:

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

The intent being that a chain of Bars is formed which can be followed, linked-list style.

This was all very well; but through some misguided notion I wanted to cut it down by a line, compounding the assignments at the end of the loop into a single line.

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

Because bar = bar.next_bar = next_bar will expand to bar.next_bar = next_bar followed by effectively bar = bar.next_bar. (Except that it doesn't.)

The problem is, this doesn't work; the "initial bar" returned does not have its next_bar defined. I can easily enough work around it by going back to the more explicit two-line solution, but what's going on?

解决方案

It's time to pull out 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        

If you look closely at that, you'll see that in the critical line (line 5, see the numbers on the left, positions 31-47), it does this:

  • Load next_bar (31) twice (34);
  • Write it (the first copy on the stack) to bar (35);
  • Write it (the second copy on the stack) to bar.next_bar (38, 41).

This is seen more obviously in a minimal test case.

>>> 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        

See what it's doing. This means that b = c = d is actually equivalent to b = d; c = d. Normally this won't matter, but in the case mentioned originally, it does matter. It means that in the critical line,

bar = bar.next_bar = next_bar

is not equivalent to

bar.next_bar = next_bar
bar = next_bar

But rather to

bar = next_bar
bar.next_bar = next_bar

This is, in fact, documented, in section 6.2 of the Python documentation, Simple statements, Assignment statements:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

There is also a related warning in that section which applies to this case:

WARNING: Although the definition of assignment implies that overlaps between the left-hand side and the right-hand side are ‘safe’ (for example a, b = b, a swaps two variables), overlaps within the collection of assigned-to variables are not safe! For instance, the following program prints [0, 2]:

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

It's possible to go for bar.next_bar = bar = next_bar and that does produce the initially desired result, but have pity on anyone (including the original author some time later!) who will have to read the code later and rejoice in the fact that, in words that I'm sure Tim would have used had he thought of them,

Explicit is better than a potentially confusing corner-case.

这篇关于为什么分配给多个目标(标识符/属性)会产生奇怪的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆