我的一个朋友向我展示了以下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
。文档不清楚这些操作码是否真的创建了a
的新副本,或者只是对它的引用
- SLICE命令是否复制
a
- Python实现(Cython、Jython)之间的答案是否不同
更新
这个片段显然是不可读和令人困惑的,我永远不会在实际中使用它密码我的兴趣纯粹是技术性的——切片是否复制了列表,以及答案是否在不同的情况下有所不同。
文档不清楚,因为切片不同的对象会做不同的事情。在列表的情况下,切片确实会生成一个(浅)副本1。请注意,这是独立于python实现的python列表的一个特性。在其他对象(如numpy数组)的情况下,它可能不会创建副本。
如果你想要一种更好的方法来检查列表中的所有元素是否相同,我可能会推荐:
all(lst[0] == item for item in lst)
从性能的角度来看,对于小列表,您朋友的代码实际上可能优于此,因为列表切片是如此优化。但是,这让IMHO更容易判断发生了什么,一旦发现不匹配,它就有机会"短路"。
1要查看的实际函数是list_subscript
,但在大多数情况下,它只调用list_slice
如果a
是列表、元组或字符串,len(a)
是n
,n > 0
,则每个切片(在C级别)创建一个长度为n-1
的新数组。在C级别上,CPython中的所有对象都被实现为指针,因此这些新数组包含从a
复制的n-1
指针(好吧,不是真的用于字符串——字符串表示更节省)。
但是,正如@mgilson所说,切片返回的内容取决于a
的类型。某些类型可能会返回一个紧凑描述符,而不是复制任何内容。一个类型甚至可能以这样一种方式实现切片,即所显示的代码无法按预期工作。
但你心里确实有一份清单;-)
是的,对于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)
这不提供短路,但在我的电脑上,对于1000个元素的列表,它比基于all
的解决方案快大约75倍,所有元素都相等,比x[1:] == x[:-1]
快大约6倍。
我发现它也比x[1:] == x[:-1]
可读一些,但这可能是品味的问题。
对于普通列表,切片确实会创建一个副本。您可以使用迭代来阻止复制:
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))
创建一个生成器,该生成器将一次一个地向all
返回a中的每个元素是否等于下一个元素。结果是一个接一个地评估的,而不是先放在列表中,这样可以节省内存,但会牺牲一些性能。