我的一位朋友向我展示了以下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+1
和SLICE+2
。 The documentation is unclear关于这些操作码是否实际创建了a
的新副本,或仅仅是对它的引用。
a
? 更新
这个片段显然难以辨认和混乱,我不会在实际中使用它 码。我的兴趣纯粹是技术性的 - 是否切片复制列表,以及答案在不同情况下是否有所不同。
答案 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)
是n
和n > 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
。结果将逐一评估,而不是先放入列表中,因此您可以节省内存,但会牺牲一些性能。