Python的交换符号中,顺序是否重要?(a, b = b, a)

7

我一直在解决一个编码面试问题,看起来像这样:

给定一个字符数组 A 和一个整数数组 P,其中 P[i] 表示排列中第 i 个元素的位置。例如,当 A = <a, b, c, d>P = <2, 0, 1, 3> 时,A 应该变为 <b, c, a, d>

我的解决方案如下:

for i in range(len(A)):
    while perm[i] != i:
        A[i], A[perm[i]] = A[perm[i]], A[i]
        perm[i], perm[perm[i]] = perm[perm[i]], perm[i]

这个代码会陷入无限循环,而下面的代码则正常工作。

for i in range(len(A)):
    while perm[i] != i:
        A[perm[i]], A[i] = A[i], A[perm[i]] 
        perm[perm[i]], perm[i] = perm[i], perm[perm[i]]

我一直以为在 Python 中,交换变量的快捷方式中的顺序并不重要,但是我很困惑为什么上面那个不起作用而下面那个却可以。

有什么想法吗?


顺序很重要。赋值语句左侧的值从左到右进行赋值。 - Michael Butscher
2个回答

8

顺序有点重要,而您编写的代码会让它有所关联。在任何赋值发生之前,整个右侧都会被完全计算,因此在简单情况下,这并不重要。但是比较以下两种情况:

perm[i], perm[perm[i]] = perm[perm[i]], perm[i]

to:

perm[perm[i]], perm[i] = perm[i], perm[perm[i]]

第一个代码中对perm[i]的赋值会影响到在对perm[perm[i]]的赋值时读取perm[i]的值;而在第二个代码中,对perm[perm[i]]的赋值使用的是perm[i]的旧值来确定赋值位置,然后再对perm[i]的新值进行赋值。
这是因为赋值是从左到右执行的;按顺序执行的步骤如下:
  1. 构造所有右侧值的tuple(在当前解释器中实际上没有构造tuple,但在逻辑上会发生这种情况)
  2. 对左目标进行赋值(包括所有必要的读操作以确定赋值位置)
  3. 对右目标进行赋值
基本上,你有一个问题,因为在赋值的左侧和不同的顺序中同时读取和写入了同一个值。

2

是的,顺序很重要,因为你正在更改从同一列表中读取的索引处的 perm 列表的内容。如果相同索引处的值发生变化,则以不同的顺序进行赋值可能会产生不同的结果。

这是它编译成的字节码:顺序是(读取,读取,读取,写入,读取,写入)。

>>> dis.dis('perm[i], perm[perm[i]] = perm[perm[i]], perm[i]')
  1           0 LOAD_NAME                0 (perm)
              3 LOAD_NAME                0 (perm)
              6 LOAD_NAME                1 (i)
              9 BINARY_SUBSCR                          # reads from perm
             10 BINARY_SUBSCR                          # reads from perm
             11 LOAD_NAME                0 (perm)
             14 LOAD_NAME                1 (i)
             17 BINARY_SUBSCR                          # reads from perm
             18 ROT_TWO
             19 LOAD_NAME                0 (perm)
             22 LOAD_NAME                1 (i)
             25 STORE_SUBSCR                           # writes to perm
             26 LOAD_NAME                0 (perm)
             29 LOAD_NAME                0 (perm)
             32 LOAD_NAME                1 (i)
             35 BINARY_SUBSCR                          # reads from perm
             36 STORE_SUBSCR                           # writes to perm
             37 LOAD_CONST               0 (None)
             40 RETURN_VALUE

这是另一种方式:顺序为(读、读、读、读、写、写)。
>>> dis.dis('perm[perm[i]], perm[i] = perm[i], perm[perm[i]]')
  1           0 LOAD_NAME                0 (perm)
              3 LOAD_NAME                1 (i)
              6 BINARY_SUBSCR                          # reads from perm
              7 LOAD_NAME                0 (perm)
             10 LOAD_NAME                0 (perm)
             13 LOAD_NAME                1 (i)
             16 BINARY_SUBSCR                          # reads from perm
             17 BINARY_SUBSCR                          # reads from perm
             18 ROT_TWO
             19 LOAD_NAME                0 (perm)
             22 LOAD_NAME                0 (perm)
             25 LOAD_NAME                1 (i)
             28 BINARY_SUBSCR                          # reads from perm
             29 STORE_SUBSCR                           # writes to perm
             30 LOAD_NAME                0 (perm)
             33 LOAD_NAME                1 (i)
             36 STORE_SUBSCR                           # writes to perm
             37 LOAD_CONST               0 (None)
             40 RETURN_VALUE

第一个能够读取在同一行中写入的值,因为它有一个写操作后跟着一个读操作;但是第二个无法实现,因为它在写入任何内容之前就完成了所有读操作。
说实话,我认为你不应该编写这样的代码,因为即使仅凭运气它也能实现你想要的功能,它也是一个难题——很难确定它应该做什么,更别提它到底做了什么。声明一个变量,如j = perm [i],然后编写perm[i],perm[j] = perm[j],perm[i],代码将易于理解,绝对不会出现魔法般的效果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接