切片`a`(例如`a [1:] == a [: - 1]`)是否会创建`a`的副本?

时间:2013-12-08 06:44:44

标签: python cpython memory-efficient python-internals

我的一位朋友向我展示了以下Python代码:

a[1:] == a[:-1]

如果a中的所有项目都相同,则返回True。

我认为代码一开始很难理解,而且 - 内存使用效率低下,因为会创建两个a副本用于比较。

我已经使用Python的dis来了解a[1:]==a[:-1]背后发生的事情:

>>> def stanga_compare(a):
...     return a[1:]==a[:-1]
...
>>> a=range(10)
>>> stanga_compare(a)
False
>>> a=[0 for i in range(10)]
>>> stanga_compare(a)
True

>>> dis.dis(stanga_compare)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (1)
              6 SLICE+1
              7 LOAD_FAST                0 (a)
             10 LOAD_CONST               2 (-1)
             13 SLICE+2
             14 COMPARE_OP               2 (==)
             17 RETURN_VALUE

归结为两个切片命令 - SLICE+1SLICE+2The documentation is unclear关于这些操作码是否实际创建了a的新副本,或仅仅是对它的引用。

  • SLICE命令是否复制a
  • Python实现(Cython,Jython)的答案是否有所不同?

更新

这个片段显然难以辨认和混乱,我不会在实际中使用它 码。我的兴趣纯粹是技术性的 - 是否切片复制列表,以及答案在不同情况下是否有所不同。

4 个答案:

答案 0 :(得分:11)

文档不清楚,因为切片不同的对象做了不同的事情。 In the case of a list, slicing does make a (shallow) copy 1 。请注意this is a feature of python lists independent of python implementation。对于其他对象(如numpy数组),它可能不会创建副本。

如果您想要更好的方法来检查列表中的所有元素是否相同,我可能会建议:

 all(lst[0] == item for item in lst)

从性能角度来看,由于列表切片得到了优化,因此您朋友的代码实际上可能优于小型列表。但是,这是恕我直言,更容易分辨出发生了什么,并且一旦发现不匹配就有机会“短路”。

1 要查看的实际功能是list_subscript,但在大多数情况下,只需调用list_slice

答案 1 :(得分:3)

如果a是列表或元组或字符串,len(a)nn > 0,则每个切片创建(在C级别)新的长度数组n-1。在C级别,CPython中的所有对象都被实现为指针,因此这些新数组包含从n-1复制的a指针(好吧,不是真的用于字符串 - 字符串表示更节俭)。

但是,正如@mgilson所说,切片返回的是a的类型。某些类型可能返回一个紧凑的描述符而不是复制任何东西。并且类型甚至可以以这样的方式实现切片,使得所示的代码不能按预期工作。

但你真的有一个清单; - )

答案 2 :(得分:3)

是的,对于list个对象,Python在切片时会创建浅拷贝,但是循环是用C语言(对于cpython)进行的,因此要比用Python编写的任何东西快得多。相同。在C中为浅拷贝循环两次并再次循环进行比较将比在Python中循环更快。

请记住,cpython通常足够快,但Python代码仍然比C代码慢大约100倍。因此,如果你想要一点速度,让cpython为你做循环是更好的。请注意,即使Python中的c = a + b也意味着执行大量逻辑(包括分支和内存分配)。

另一方面,如果对于你的代码这种微优化是基础那么可能Python不是你正在解决的问题的正确工具,你应该考虑其他选项(比如写一个与sip接口的小型C ++扩展,使用Cython,PyPy ......)。

确保代码对于未经训练的眼睛是不可读的,并且如果列表很长且通常常数,那么all(y == x[0] for y in x)会因为短路而更快(即使循环在Python中,并为每个元素执行额外的下标操作。)

可读性很重要。很多。

修改

使C代码循环遍历元素的另一个有趣选择是

x and x.count(x[0]) == len(x)

这不提供短路,但在我的电脑上比基于all的解决方案快了大约75倍,对于1000个元素的列表,它们都相等,比{{1快6倍}}

我发现它比x[1:] == x[:-1]更具可读性,但这可能是一种品味问题。

答案 3 :(得分:0)

对于vanilla列表,切片会创建一个副本。您可以使用迭代来阻止复制:

import itertools
a1 = iter(a)
a2 = iter(a)
a2.next() # start a2 iterator one removed
all_are_identical = all((i1 == i2 for i1, i2 in itertools.izip(a1, a2)))

(i1 == i2 for i1, i2 in itertools.izip(a1, a2))创建一个生成器,它将返回a中的每个元素是否等于下一个元素,一次一个,到all。结果将逐一评估,而不是先放入列表中,因此您可以节省内存,但会牺牲一些性能。