Python:为什么列表元素在使用程序后仍然没有消失?

Python: Why still list elements are not disappeared after using procedure?

我定义这个函数来做:[1,2,3] --> [2,3,1]

def shift_to_left(p):
    p.append(p[0])
    return p[1:]       

当我这样检查时,结果没问题:

p1 = [1,2,3]
print p1
p1 = shift_to_left(p1)
print p1

The result:
[1, 2, 3]
[2, 3, 1]

但是,当我引入另一个列表并在进行时连接结果不同:

ss = []
p1 = [1,2,3]
ss.append(p1)
p1 = shift_to_left(p1)
ss.append(p1)

print ss

The result
[[1, 2, 3, 1], [2, 3, 1]]

But I want: 
[1,2,3]
[2,3,1]

为什么会这样?

非常感谢,

如果你 运行 你的代码 here,你会注意到 ss 仍然指向原始的(由于 p.append(p[0]) 在你的 shift 函数中发生了变化)副本p1,其中 p1 在重新分配时一起指向已知列表,从而导致行为。 (11 步中的第 10 步)

(p发生变异,ss[0] = p)

(p1 被分配到一个新的列表,后来附加到 ss)

why is it happening?

return p[1:] 是 "non-destructive":它创建一个新列表。然而,p.append(p[0]) 是 "destructive":它改变了 p 本身。

首先,您将 p1 附加到 ss。这使得 [[1, 2, 3]],其中 [1, 2, 3]p1.

然后执行 shift_to_left,将 p1 更改为 [1, 2, 3, 1] 和 returns [2, 3, 1]。因为 p1 包含在 ss 中,你的 ss 变成 [[1, 2, 3, 1]],然后你追加新的 p1 形成 [[1, 2, 3, 1], [2, 3, 1]].

更好的实现是完全无损的:

def shift_to_left(p):
    return p[1:] + [p[0]]

如果你想 shift/rotate 列表中的元素,我认为更好的方法是使用 deque,而不是重新发明轮子。例如:

from collections import deque
d = deque([1,2,3])
d.rotate(-1)
print(d) 
#[2, 3, 1]

在Python中,大部分论点都是引用。

您的函数 shift_to_left 实际上改变了它的参数(通过使用追加),但随后 returns 一个切片(它是列表的浅表副本)。

当您将原始变量替换为 shift_to_left 的输出时,此行为将被隐藏:

In [1]: def shift_to_left(p):
   ...:     p.append(p[0])
   ...:     return p[1:]
   ...: 

In [2]: xs = [1, 2, 3]

In [3]: xs = shift_to_left(xs)

In [4]: xs
Out[4]: [2, 3, 1]

但是如果我们将结果赋给一个 new 变量,我们可以看到原来的列表确实已经改变了:

In [5]: ys = shift_to_left(xs)

In [6]: ys
Out[6]: [3, 1, 2]

In [7]: xs
Out[7]: [2, 3, 1, 2]

我们的结果 ysxs 从第二个元素开始的切片。这就是你所期望的。

但是 xs 本身 也被调用 append 改变了 :它现在比以前长了一个元素。 这就是您在第二个示例中遇到的情况。


如果您想要这种行为,避免这种情况的一种方法是将列表的副本传递给shift_to_left:

In [8]: zs = shift_to_left(ys[:])

In [9]: zs
Out[9]: [1, 2, 3]

In [10]: ys
Out[10]: [3, 1, 2]

在这里,您可以看到原始列表 ys 没有被修改,因为 shift_to_left 得到了它的副本,而不是对象本身。 (当然,这仍然是通过引用传递;它只是不是对 ys 的引用)。


或者,可能更合理的是,您可以更改 shift_to_left 本身,这样它就不会修改其参数:

def shift_to_left(xs):
    return xs[1:] + xs[0]  # build a new list in the return statement

这两种方法的一个大问题是它们会创建大量列表副本,当列表很大时,这可能会非常慢(并使用大量内存)。


当然,正如@Marcin 指出的那样,如果这不仅仅是一项学术练习,您可能应该使用一种内置数据结构,例如 deque.

试试这个:

  p1 = [1,2,3]
  p1 = shift_to_left(p1)
  ss = []
  ss.extend(p1)

  print ss

打印 [2, 3, 1]。使用 extend() 代替,因为 append() 将在数组中创建一个数组。您还额外调用了 ss.append(p1).