Python:为什么仍然列表元素在使用过程后不会消失?

Python:为什么仍然列表元素在使用过程后不会消失?,python,list,Python,List,我将此函数定义为执行:[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 但是,当我引入另一个列表并在进行连接时,结果是不同的: ss = [] p1 = [1,2,3] ss.append(p1) p1 = shift_to_left(p

我将此函数定义为执行:[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

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

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

print ss

为什么会这样

非常感谢,

如果您运行代码,您会注意到
ss
仍然指向
p1
的原始副本(由于
p.append(p[0])
),其中当它被重新分配时,
p1
一起指向已知列表,从而导致该行为。(第10步,共11步)

p
发生突变,并且
ss[0]=p

p1
被分配给一个新的列表,后者被附加到
ss

为什么会这样

returnp[1:][/code>是“非破坏性的”:它创建一个新列表。然而,
p.append(p[0])
是“破坏性的”:它改变了
p
本身

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

然后执行
左移
,将
p1
更改为
[1,2,3,1]
,并返回
[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]]

如果您想移动/旋转列表中的元素,我认为最好使用a,而不是重新发明轮子。例如:

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

在Python中,大多数参数都是通过引用获取的

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

当您将原始变量替换为
shift\u to\u 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]
但如果我们将结果赋给一个新变量,我们可以看到原来的列表确实发生了变化:

In [5]: ys = shift_to_left(xs)

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

In [7]: xs
Out[7]: [2, 3, 1, 2]
我们的结果,
ys
,是从第二个元素开始的
xs
切片。这正是你所期望的

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


如果您不希望出现这种行为,可以通过将列表副本传递给
shift\u to\u 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\u to\u left
得到了它的副本,而不是对象本身。(当然,这仍然是通过引用传递的;它只是没有引用到
ys


或者,可能更合理地说,您可以将
shift\u更改为\u 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)

返回p[1:]
不会修改原始列表。其他人已经以多种方式解释了发生的情况,因此我将只提及函数的最简单正确实现:
返回p[1:::+[p[0]
检查id:print
id(p1)
通话前后,以及
ss
内容的ID。非常感谢各位,这是我第一次发布问题。答案和动态社区给我留下了深刻的印象。解决了问题,但没有回答问题(“为什么”):)Meh,+1。尽管我同意,但问题本身与“为什么”有关。像这样的问题在大学考试中很常用。因此,深入理解为什么与备选方案相反是非常重要的,尤其是在这种情况下。@Amadan你说得对,我认为其他Anwser,很好地回答了OP问题。但是使用deque可能更快、更容易、更不容易出错,并且通常是OP想要实现的更推荐的方法。他想要数组中的一个数组,理想的解决方案(格式很差,但仍然声明)是
[[1,2,3],[2,3,1]]
。这是一个很好的教育工具。@Amadan:这个东西救了我很多次。尤其是指针相关的东西。我非常感谢这个人。“在Python中,大多数参数都是通过引用获取的”是不正确的。在Python中,所有参数都是通过赋值传递的(如果愿意,也可以通过对象传递)。有关解释,请参阅。@KirkStrauser除非我误解了这篇文章,它是说通过赋值传递是通过对可变类型(即,对于大多数类型)的引用。要记住的最大一点是,传递的是变量指向的对象,而不是变量本身。这是一个微妙但重要的区别。
def shift_to_left(xs):
    return xs[1:] + xs[0]  # build a new list in the return statement
  p1 = [1,2,3]
  p1 = shift_to_left(p1)
  ss = []
  ss.extend(p1)

  print ss