在Python上列出变异

时间:2013-02-10 14:19:59

标签: python

我尝试通过使用第一个元素交换列表和另一个引用列表之间的公共元素来改变列表。实施如下所示:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> L[0], L[L.index(A[0])] = L[L.index(A[0])], L[0] #want to swap 3 with 1  
>>> L 
[1,2,3,4,5,6,7,8,9,] #List L was not mutated  

列表没有像我预期的那样发生变异。但是当我修改如下所示的实现时,它起作用了:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> i = L.index(A[0])
>>> L[0], L[i] = L[i], L[0]
>>> L
[3,2,1,4,5,6,7,8,9,] #Now list mutated as desired even though L[i] and L[L.index(A[0])] evaluate to same value.  

我的问题是,为什么第一个作业不能改变名单?我想到了,但我的脑子却在解释它。

2 个答案:

答案 0 :(得分:12)

虽然在Python中,在进行多项分配时,右侧是首先,但如果左侧分配目标在其中有表达式,则会对其进行评估在分配时一个接一个。

如果相反,他们会首先按照您的预期进行评估,但这当然有效。

assignment statements section

中记录了这一点
  

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

  

如果目标列表是以逗号分隔的目标列表:对象必须是一个可迭代的项目数与目标列表中的目标相同,并且项目是从左到右分配的,到相应的目标

强调我的。从左到右在这里至关重要。 L[0]被分配到L[L.index(3)]之前。

然后,文档会详细介绍订阅目标会发生什么,例如L[0]L[L.index(3)]

  

如果目标是订阅:评估引用中的主表达式。它应该产生可变序列对象(例如列表)或映射对象(例如字典)。 接下来,评估下标表达式。

再次强调我的;下标表达式是单独评估的,并且由于目标列表是从左到右评估的,因此评估在上一次分配到L[0] 后发生

你可以通过反汇编python代码来看到这个:

>>> import dis
>>> def f(L):
...     L[0], L[2] = L[2], L[0]
... 
>>> def g(L):
...     L[0], L[L.index(3)] = L[L.index(3)], L[0]
... 
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (L)   # L[2]
              3 LOAD_CONST               1 (2)
              6 BINARY_SUBSCR       
              7 LOAD_FAST                0 (L)   # L[0]
             10 LOAD_CONST               2 (0)
             13 BINARY_SUBSCR       
             14 ROT_TWO             
             15 LOAD_FAST                0 (L)   # Store in L[0]
             18 LOAD_CONST               2 (0)
             21 STORE_SUBSCR        
             22 LOAD_FAST                0 (L)   # Store in L[2]
             25 LOAD_CONST               1 (2)
             28 STORE_SUBSCR        
             29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        
>>> dis.dis(g)
  2           0 LOAD_FAST                0 (L)   # L[L.index(3)]
              3 LOAD_FAST                0 (L)
              6 LOAD_ATTR                0 (index)
              9 LOAD_CONST               1 (3)
             12 CALL_FUNCTION            1
             15 BINARY_SUBSCR       
             16 LOAD_FAST                0 (L)  #  L[0]
             19 LOAD_CONST               2 (0)
             22 BINARY_SUBSCR       
             23 ROT_TWO             
             24 LOAD_FAST                0 (L)  # Store in L[0]
             27 LOAD_CONST               2 (0)
             30 STORE_SUBSCR        
             31 LOAD_FAST                0 (L)  # Store in L[L.index(3)]
             34 LOAD_FAST                0 (L)
             37 LOAD_ATTR                0 (index)
             40 LOAD_CONST               1 (3)
             43 CALL_FUNCTION            1
             46 STORE_SUBSCR        
             47 LOAD_CONST               0 (None)
             50 RETURN_VALUE        

存储操作首先存储L[0] = 3,因此下一次调用L.index(3)会返回0,因此1会立即存回0位置!

以下工作正常:

L[L.index(3)], L[0] = L[0], L[L.index(3)]

因为现在首先完成L.index(3)查找。但是,最好将.index()调用的结果存储在临时变量中,因为在任何情况下,不调用.index()两次会更有效。

答案 1 :(得分:7)

问题是这两者并不相同。第一个例子类似于:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> i = L.index(A[0])
>>> L[0] = L[i]
>>> i = L.index(A[0])
>>> L[i] = L[0]

这意味着您最终交换,然后找到刚刚交换的元素并重新交换。

你感到困惑的原因是你正在考虑元组赋值,因为Python同时做两件事 - 这不是执行执行的方式,而是按顺序执行,这会改变结果。< / p>

值得注意的是,即使它确实起作用,这也是一种次优的方式。 list.index()并不是一个特别快的操作,所以无缘无故地做两次并不是一个好主意。