【问题标题】:Why is reversing a list with slicing slower than reverse iterator为什么使用切片反转列表比反向迭代器慢
【发布时间】:2013-05-09 15:07:18
【问题描述】:

在 Python 中至少有两种方法可以反转列表,但迭代器方法要快得多(至少在 Python 2.7.x 中)。我想了解造成这种速度差异的原因。

>>> x = range(1000)
>>> %timeit x[::-1]
100000 loops, best of 3: 2.99 us per loop
>>> %timeit reversed(x)
10000000 loops, best of 3: 169 ns per loop

我怀疑速度差异至少是由于以下原因:

  1. reversed 是用 C 编写的
  2. reversed 是一个迭代器,因此内存开销更少

我尝试使用dis 模块来更好地了解这些操作,但这并没有太大帮助。我不得不将这些操作放在一个函数中来反汇编它们。

>> def reverselist(_list):
...     return _list[::-1]
... 
>>> dis.dis(reverselist)
  2           0 LOAD_FAST                0 (_list)
              3 LOAD_CONST               0 (None)
              6 LOAD_CONST               0 (None)
              9 LOAD_CONST               1 (-1)
             12 BUILD_SLICE              3
             15 BINARY_SUBSCR       
             16 RETURN_VALUE
>>> def reversed_iter(_list):
...     return reversed(_list)
... 
>>> dis.dis(reversed_iter)
  2           0 LOAD_GLOBAL              0 (reversed)
              3 LOAD_FAST                0 (_list)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE        

在切片操作期间到底发生了什么,是否有很多内存开销?也许切片是用纯 Python 实现的?

【问题讨论】:

  • 附带说明,我不必为了使用dis 模块而将这些操作放入方法中。 This post 有一个漂亮的小 lambda,而不是先编译代码字符串(必需),然后将其传递给 dis.dis

标签: python


【解决方案1】:

这是因为reversed 返回一个iterator,而切片返回一个完整列表。

>>> lis = range(10)
>>> lis[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> reversed(lis)
<listreverseiterator object at 0x909dd0c>

您必须使用 list() 将该迭代器转换为整个列表:

>>> lis = range(10**5)
>>> %timeit lis[::-1]
100 loops, best of 3: 2.8 ms per loop
>>> %timeit list(reversed(lis))
100 loops, best of 3: 3.13 ms per loop

帮助reversed

>>> reversed?
Type:       type
String Form:<type 'reversed'>
Namespace:  Python builtin
Docstring:
reversed(sequence) -> reverse iterator over values of the sequence

Return a reverse iterator

【讨论】:

  • 抱歉跑题了,? 在哪个 python 版本中可以寻求帮助?
  • 天哪,这似乎很明显,显然这对我来说还为时过早。谢谢你的解释!
  • @septi ? 在 Ipython shell 中,而不是特定版本的 Python。
  • @septi 这是 IPython 外壳。
【解决方案2】:

reversed() 返回一个迭代器。 实际上在你遍历它之前不会反转任何东西。来自documentation

返回一个反向iterator

你需要比较一下将reversed()的结果再次转成列表所需要的时间:

%timeit list(reversed(x))

只创建迭代器(它只是对原始列表的引用和初始化为列表长度的项指针)根本不需要任何时间。

必须将reversed() 转回列表会使其变慢很多

>>> import timeit
>>> x = range(1000)
>>> timeit.timeit('x[::-1]', 'from __main__ import x')
4.623600006103516
>>> timeit.timeit('list(reversed(x))', 'from __main__ import x')
16.647125005722046

【讨论】:

  • 不知道我是怎么错过的。感谢您的精彩解释。
【解决方案3】:

reversed() 实际上并没有反转任何东西,它只是返回一个可用于以相反顺序迭代容器元素的对象。这就是为什么它比实际反转元素更快的原因。

请注意,您还有 reverse() 与切片运算符执行相同的操作。

reverse() 在原地运行。切片运算符返回一个列表对象。

【讨论】:

  • list.reverse() 就地运行而不是 reversed
猜你喜欢
  • 2023-04-07
  • 1970-01-01
  • 2019-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
相关资源
最近更新 更多