“更好”的是反转方法还是反转内置函数?

25

在使用Python中,通常认为哪个更符合Python风格/更好/更快——使用reverse()方法还是reversed()内置函数?

两者的用法示例:

_list = list(xrange(4))

print _list

rlist = list(reversed(_list))

print rlist

_list.reverse()

print _list
8个回答

56

foo.reverse()实际上是将容器中的元素反转了。而reversed()并没有真正地翻转任何内容,它只返回一个对象,可以用来迭代容器元素的相反顺序。如果这就是你需要的,那么通常比实际翻转元素更快。


18

看起来有很大的区别。我真的以为情况是相反的。为什么重新排列列表中的值比从迭代器创建新列表更快?

from decorators import bench

_list = range(10 ** 6)

@ bench
def foo():
  list(reversed(_list))

@ bench
def bar():
  _list.reverse()

foo()
bar()

print foo.time
print bar.time

0.167278051376
0.0122621059418


内存分配本身不应该花费太长时间;如果我没记错,列表是单个连续分配(PyObject**)。更可能的差异是因为list(reversed(_list)),即使list构造函数是C代码,也必须通过reverseiterator的API而不仅仅是在紧密的C循环中交换指针。 - Karl Knechtel
如果他们必须检查listreverseiterator,那么他们必须检查所有东西...最终会变得有些笨重,并减缓短复制的速度。此外,reversed预期的常见情况是你要迭代而不需要创建临时对象。 - Karl Knechtel
2
如果你对列表进行操作,图片会发生变化。比较for i in reversed(_list):..._list.reverse();for i in _list:...,前者速度稍快。 - Weidenrinde

15

根据您是否想要原地翻转列表(即更改列表)来决定。没有其他真正的区别。

通常使用reversed可以产生更好的代码。


4

在不了解性能真实统计的情况下,_list.reverse() 修改了列表本身,而reversed(_list)返回一个准备好按相反顺序遍历列表的迭代器。这本身就是一个很大的区别。

如果这不是问题,object.reverse() 对我来说更易读,但也许您有特定的速度要求。如果reverse()不属于消耗资源的80%软件,那么我不会烦恼(作为一个通用的经验法则)。


reversed 不会复制,它返回一个迭代器。 - user395760

2
  • _list.reverse()是原地反转列表,不返回值
  • reversed(_list)不改变_list,但返回一个可迭代的反向对象
  • _list[::-1]不改变_list,但返回反向切片

示例:

_list = [1,2,3]
ret1 = list(reversed(_list))
ret2 = _list[::-1] #reverse order slice
ret3 = _list.reverse() #no value set in ret3
print('ret1,ret2,ret3,_list:',ret1,ret2,ret3,_list)

_list = [1,2,3]
for x in reversed(_list):
    print(x)

输出:

ret1,ret2,ret3,_list: [3, 2, 1] [3, 2, 1] None [3, 2, 1]
3
2
1

0

@Niklas R的回答进行扩展:

import timeit

print('list.reverse() - real-list', timeit.timeit('_list.reverse()', '_list = list(range(1_000))'))
print('list.reverse() - iterator', timeit.timeit('_list = range(1_000); list(_list).reverse()'))  # can't really use .reverse() since you need to cast it first
print('reversed() - real-list', timeit.timeit('list(reversed(_list))', '_list = list(range(1_000))'))
print('reversed() - iterator', timeit.timeit('_list = range(1_000); list(reversed(_list))'))
print('list-comprehension - real-list', timeit.timeit('_list[::-1]', '_list = list(range(1_000))'))
print('list-comprehension - iterator', timeit.timeit('_list = range(1_000); _list[::-1]'))

结果:

list.reverse() - real-list 0.29828099999576807
list.reverse() - iterator 11.078685999964364  # can't really use .reverse() since you need to cast it first
reversed() - real-list 3.7131450000451878
reversed() - iterator 12.048991999938153
list-comprehension - real-list 2.2268580000381917
list-comprehension - iterator 0.4313809999730438

(越少越好/越快)


0

如果最终你打算在迭代器中修改列表,使用reversed()总是更好的选择,因为它会使列表变为不可变,并且在函数式编程中,使用不可变数据总是更好的选择。


0
  • 内置函数reversed(seq)会返回一个反向的迭代器(iterator),它是一个代表流数据的对象,这个流数据将会返回一个接着一个的元素。生成这个反向迭代器的时间/空间复杂度是O(1),使用它遍历列表中的元素时间/空间复杂度是O(N)/O(1),其中N是列表的长度。如果你只想遍历反转后的列表而不修改它,那么这就是你想要的方法,这种方法在这种情况下表现最佳。

  • _list.reverse()原地翻转列表,该操作的时间/空间复杂度是O(N)/O(1),因为它必须遍历列表的一半来翻转它们,并且不会将结果存储在新的列表中。如果您不需要保留原始列表并且在反转后的列表上有多个遍历/操作要执行,或者必须保存处理后的反转列表,则此方法很好。

  • 最后,使用切片[::-1]创建一个新的、反向排序的列表/副本。该操作的时间/空间复杂度是O(N)/O(N),因为您必须将列表的所有元素复制到新列表中,并且此新列表将消耗与原始列表相同的空间。如果您需要保留原始列表并在不同的对象中存储反向副本以进行进一步处理,则此方法非常适用。

总结一下,根据您的使用情况,您将不得不使用这三种方法之一,它们具有略微不同的目标和完全不同的性能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接