Лучший способ создать «перевернутый» список в Python?

92

Как лучше всего в Python создать новый список, элементы которого будут такими же, как и в каком-либо другом списке, но в обратном порядке? (Я не хочу изменять существующий список на месте.)

Вот одно решение, которое пришло мне в голову:

new_list = list(reversed(old_list))

Также возможно дублировать, а old_listзатем отменить дубликат на месте:

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

Есть ли лучший вариант, который я упустил из виду? Если нет, то есть ли веская причина (например, эффективность) использовать один из вышеуказанных подходов вместо другого?

Дэвидчемберс
источник

Ответы:

208
newlist = oldlist[::-1]

В [::-1]Нарезка (что моя жена Анна любит называть «марсианскую смайлик» ;-) означает: срез всей последовательности, с шагом -1, то есть в обратном. Работает для всех последовательностей.

Обратите внимание, что это ( и упомянутые вами альтернативы) эквивалентно «неглубокой копии», то есть: если элементы являются изменяемыми и вы вызываете для них мутаторы, мутации в элементах, содержащихся в исходном списке, также присутствуют в элементах в перевернутый список, и наоборот. Если вам нужно этого избежать, единственный хороший вариант - это copy.deepcopy(хотя всегда потенциально дорогостоящая операция), за которой в данном случае следует a .reverse.

Алекс Мартелли
источник
Боже мой! Это является элегантным. Еще несколько дней назад я не понимал, что можно включать «шаг» при нарезке; теперь мне интересно, как я вообще без него обходился! Спасибо, Алекс. :)
davidchambers
1
Также спасибо за упоминание о том, что это дает неглубокую копию. Но это все, что мне нужно, поэтому я собираюсь добавить в свой код марсианский смайлик.
davidchambers
13
Это действительно кажется намного более волшебным и менее читаемым, чем исходное предложение list(reversed(oldlist)). За исключением незначительных микро-оптимизации, есть ли основание предпочесть , [::-1]чтобы reversed()?
Брайан Кэмпбелл
@BrianCampbell: Что в этом "волшебства"? Если вы вообще разбираетесь в нарезке, это имеет смысл. Если вы не разбираетесь в нарезке ... ну, вам действительно стоит изучить его довольно рано в своей карьере Python. Конечно, это reversedимеет огромное преимущество, когда вам не нужен список, потому что он не тратит впустую память или время на предварительное его создание. Но когда вам действительно нужен список, использование [::-1]вместо list(reversed())аналогично использованию [listcomp]вместо list(genexpr).
abarnert
11
@abarnert В коде, с которым я работаю, я так редко вижу третий аргумент среза, что если я вижу его использование, мне приходится искать, что он означает. Как только я это сделаю, все еще не очевидно, что начальные и конечные значения по умолчанию меняются местами, когда шаг отрицательный. С первого взгляда, не [::-1]обращая внимания на значение третьего аргумента, я могу предположить, что это означает удаление последнего элемента списка, а не его обратное изменение. reversed(list)точно указывает, что он делает; он разъясняет свое намерение в смысле «Явное лучше, чем неявное», «Читабельность имеет значение» и «Разрезанное лучше, чем плотное».
Брайан Кэмпбелл
56

А теперь давайте timeit. Подсказка: у Алекса самый [::-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

Обновление: добавлен метод составления списка, предложенный инспектором G4dget. Пусть результаты говорят сами за себя.

Сэм Долан
источник
8
Просто примечание - это верно для создания списка перевернутых копий, но перевернутый все еще более эффективен для итераций, чем тогда,[::-1]
Тадг Макдональд-Дженсен
7

Корректировки

Стоит предоставить базовый тест / корректировку для расчетов timeit с помощью sdolan, которые показывают производительность «обратного» без часто ненужного 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 мксек.

Mekarpeles
источник
15
reversed () возвращает объект-итератор с отложенной оценкой, поэтому я думаю, что это не совсем корректное сравнение с нотацией срезов [:: - 1] в целом.
радужный
1
Даже в случае, когда итератор может использоваться напрямую, например ''.join(reversed(['1','2','3'])), метод среза все равно на> 30% быстрее.
dansalmo
1
Неудивительно, почему у вас одинаковый результат первых двух тестов: они идентичны!
MestreLion
Мои результаты больше похожи на это. ol [:: - 1] занимает примерно вдвое больше времени, чем list (reversed (ol)). Метод ol [:: - 1] требует для записи меньшего количества символов. Однако list (reversed (ol)), вероятно, более читается для начинающих программистов на Python, и на моей машине он работает быстрее.
dhj