11

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

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

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

我认为代码乍一看很难理解,而且 - 它在内存使用方面效率低下,因为a将创建两个副本用于比较。

我使用 Pythondis来查看幕后发生的事情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)而异?

更新

这个片段显然是不可读和令人困惑的,我不会在实际代码中使用它。我的兴趣纯粹是技术性的——切片是否复制列表,以及答案是否在不同情况下有所不同。

4

4 回答 4

11

文档不清楚,因为切片不同的对象会做不同的事情。 在列表的情况下,切片确实会生成(浅)副本1。请注意,这是独立于 python 实现的 python 列表的一个特性。对于其他对象(如 numpy 数组),它可能不会创建副本。

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

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

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

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

于 2013-12-08T06:52:22.133 回答
3

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

但是,正如@mgilson 所说,切片返回的内容取决于a's 类型。某些类型可能会返回一个紧凑的描述符而不是复制任何内容。并且一个类型甚至可能以这样一种方式实现切片,即显示的代码不会按预期工作。

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

于 2013-12-08T06:59:58.510 回答
3

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

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

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

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

可读性很重要。很多。

编辑

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

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

这不提供短路,但在我的电脑上比all基于 - 的解决方案的 1000 个元素的列表快大约 75 倍,它们都相等,比x[1:] == x[:-1].

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

于 2013-12-08T07:29:15.210 回答
0

对于普通列表,切片确实会创建一个副本。您可以改为使用迭代来防止复制:

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 中的每个元素是否等于下一个元素,一次一个,to all。结果是逐个评估的,而不是首先放入列表中,因此您以牺牲一些性能为代价来节省内存。

于 2013-12-08T07:17:16.053 回答