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



我的一个朋友向我展示了以下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+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)nn > 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中的每个元素是否等于下一个元素。结果是一个接一个地评估的,而不是先放在列表中,这样可以节省内存,但会牺牲一些性能。

相关内容

  • 没有找到相关文章

最新更新