Python中创建“反向”列表的最佳方法是什么?

105
在Python中,创建一个新列表,其项与另一个列表的项相同,但顺序相反,最好的方法是什么?(我不想在原地修改现有列表。)
以下是我想到的一种解决方案:
new_list = list(reversed(old_list))

还有一种方法是复制old_list,然后就地逆转该副本:

new_list = list(old_list) # or `new_list = old_list[:]`
new_list.reverse()

我有没有忽略掉更好的选项?如果没有,那么是否有令人信服的理由(例如效率)来使用上述方法之一而不是另一种方法?

3个回答

232
newlist = oldlist[::-1]

[::-1]切片(我妻子Anna喜欢称之为“火星笑脸”;-)表示:对整个序列进行切片,步长为-1,即反向。它适用于所有序列。

请注意,这个切片(以及您提到的其他替代方法)等价于“浅拷贝”,即:如果元素是可变的并且您在其中调用mutator,则原始列表中保留的元素中的更改也会出现在翻转列表中的元素中,反之亦然。 如果您需要避免这种情况,那么copy.deepcopy(虽然这总是一个潜在的高成本操作),后跟.reverse,是唯一的好选择。


哦,我的天!那真是太优雅了。直到几天前,我才意识到在切片时可以包含一个“步骤”,现在我在想着以前怎么没有用过它!谢谢,Alex. :) - davidchambers
1
谢谢你提到这会产生一个浅拷贝。这正是我所需要的,所以我准备在我的代码中添加一个火星笑脸。 - davidchambers
16
使用[::-1]list(reversed(oldlist))看起来更神奇也更难读懂,除了微小的性能优化外,有没有其他理由更喜欢[::-1]而不是reversed() - Brian Campbell
2
@BrianCampbell:它有什么"神奇"的地方呢?如果你完全理解切片,它就是完全有道理的。如果你不理解切片......那么你在Python职业生涯中就应该尽早学习它。当然,在您不需要列表时,reversed具有巨大的优势,因为它不会浪费内存或在前期构建列表时浪费时间。但是,在您需要列表时,使用[::-1]而不是list(reversed())类似于使用[listcomp]而不是list(genexpr) - abarnert
15
在我处理的代码中,很少看到第三个切片参数的使用。如果我看到了它的使用,我就不得不查一下它的含义。即使我知道了它的含义,当步长为负数时默认的起始和结束值交换仍然不是很明显。快速浏览代码时,如果没有查找第三个参数的含义,我可能会猜测 [::-1] 的意思是删除列表的最后一个元素,而不是将其翻转。 reversed(list) 明确说明了它正在做什么;它以 "显式优于隐式"、"可读性至上" 和 "简洁胜于繁琐" 的意义来阐述其意图。 - Brian Campbell

65

现在让我们来使用timeit提示: Alex的[::-1]是最快的 :)。

$ p -m timeit "ol = [1, 2, 3]; nl = list(reversed(ol))"
100000 loops, best of 3: 2.34 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = list(ol); nl.reverse();"
1000000 loops, best of 3: 0.686 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = ol[::-1];"
1000000 loops, best of 3: 0.569 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = [i for i in reversed(ol)];"
1000000 loops, best of 3: 1.48 usec per loop


$ p -m timeit "ol = [1, 2, 3]*1000; nl = list(reversed(ol))"
10000 loops, best of 3: 44.7 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = list(ol); nl.reverse();"
10000 loops, best of 3: 27.2 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = ol[::-1];"
10000 loops, best of 3: 24.3 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = [i for i in reversed(ol)];"
10000 loops, best of 3: 155 usec per loop

更新:添加了由inspectorG4dget建议的列表推导式方法。我会让结果说明一切。


9
注意:这是用于创建反转副本列表的准确方法,但在迭代方面,reversed 仍比 [::-1] 更有效。 - Tadhg McDonald-Jensen

11

调整

值得提供一个基准标准/调整时间测量,用于sdolan展示'reversed'的性能,而不需要通常不必要的list()转换。这个list()操作会增加额外的26微秒运行时间,只有在迭代器不可接受的情况下才需要。

结果:

reversed(lst) -- 11.2 usecs

list(reversed(lst)) -- 37.1 usecs

lst[::-1] -- 23.6 usecs

计算:

# I ran this set of 100000 and came up with 11.2, twice:
python -m timeit "ol = [1, 2, 3]*1000; nl = reversed(ol)"
100000 loops, best of 3: 11.2 usec per loop

# This shows the overhead of list()
python -m timeit "ol = [1, 2, 3]*1000; nl = list(reversed(ol))"
10000 loops, best of 3: 37.1 usec per loop

# This is the result for reverse via -1 step slices
python -m timeit "ol = [1, 2, 3]*1000;nl = ol[::-1]"
10000 loops, best of 3: 23.6 usec per loop

结论:

这些测试的结论是,reversed() 比切片 [::-1] 快12.4微秒。


16
reversed() 返回一个惰性求值的迭代器对象,因此我认为与通常的切片表达式 [::-1] 进行比较并不公平。 - iridescent
1
即使在可以直接使用迭代器的情况下,例如 ''.join(reversed(['1','2','3'])),切片方法仍然比迭代器快30%以上。 - dansalmo
1
难怪前两次测试结果一样:它们完全相同! - MestreLion
我的结果更接近这个。ol[::-1] 的执行时间大约是 list(reversed(ol)) 的两倍。ol[::-1] 方法编写的字符较少。然而,list(reversed(ol)) 对于初学者来说可能更易读,在我的机器上也更快。 - dhj

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