Melhor maneira de criar uma lista “reversa” em Python?

89

Em Python, qual é a melhor maneira de criar uma nova lista cujos itens são iguais aos de alguma outra lista, mas na ordem inversa? (Não quero modificar a lista existente no local.)

Aqui está uma solução que me ocorreu:

new_list = list(reversed(old_list))

Também é possível duplicar e, em old_listseguida, inverter a duplicata no local:

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

Existe uma opção melhor que eu esqueci? Se não, há um motivo convincente (como eficiência) para usar uma das abordagens acima em vez da outra?

Davidchambers
fonte

Respostas:

205
newlist = oldlist[::-1]

O [::-1]fatiar (que minha esposa Anna gosta de chamar de "o smiley marciano" ;-) significa: fatiar toda a sequência, com um passo de -1, ou seja, ao contrário. Funciona para todas as sequências.

Observe que isso ( e as alternativas que você mencionou) é equivalente a uma "cópia superficial", ou seja: se os itens são mutáveis ​​e você chama modificadores neles, as mutações nos itens mantidos na lista original também estão nos itens da lista invertida e vice-versa. Se você precisar evitar isso, um copy.deepcopy(embora sempre uma operação potencialmente cara), seguido neste caso por um .reverse, é a única boa opção.

Alex Martelli
fonte
Oh meu! Isso é elegante. Até poucos dias atrás eu não sabia que era possível incluir uma "etapa" ao fatiar; agora estou me perguntando como consegui sobreviver sem ele! Obrigado, Alex. :)
davidchambers
1
Obrigado, também, por mencionar que isso produz uma cópia superficial. Isso é tudo que eu preciso, então estou prestes a adicionar um smiley marciano ao meu código.
davidchambers
13
Isso realmente parece muito mais mágico e muito menos legível do que a sugestão original list(reversed(oldlist)). À excepção de um menor micro-otimização, há alguma razão para preferir [::-1]a reversed()?
Brian Campbell
@BrianCampbell: O que há de "mágico" nisso? Se você entende como cortar, faz todo o sentido. Se você não entende de fatiamento ... bem, você realmente deve aprender bem cedo em sua carreira em Python. É claro que reversedtem uma grande vantagem quando você não precisa da lista, porque não desperdiça memória ou tempo inicial para criá-la. Mas quando você fazer precisar da lista, utilizando [::-1], em vez de list(reversed())é análogo ao uso de uma [listcomp]vez de list(genexpr).
abarnert
10
@abarnert No código com o qual trabalho, raramente vejo o argumento da terceira fatia, se vejo um uso dele, tenho que pesquisar o que significa. Depois de fazer isso, ainda não é óbvio que os valores padrão inicial e final são trocados quando a etapa é negativa. Em uma rápida olhada, sem procurar o significado do terceiro argumento, posso supor que isso [::-1]significa remover o último elemento de uma lista, em vez de invertê-lo. reversed(list)afirma exatamente o que está fazendo; ele explicita sua intenção, no sentido de "Explícito é melhor do que implícito", "A legibilidade conta" e "Esparso é melhor do que denso".
Brian Campbell
56

Agora vamos timeit. Dica: Alex [::-1]é o mais rápido :)

$ 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

Atualização: método de comp de lista adicionado sugerido por inspectorG4dget. Vou deixar os resultados falarem por si.

Sam Dolan
fonte
8
Apenas uma observação - isso é preciso para criar lista de cópia invertida, mas invertida é ainda mais eficiente para iteração do que[::-1]
Tadhg McDonald-Jensen
7

Ajustes

Vale a pena fornecer um benchmark / ajuste de linha de base para os cálculos de timeit por sdolan que mostram o desempenho de 'reverso' sem a list()conversão frequentemente desnecessária . Esta list()operação adiciona mais 26 usecs ao tempo de execução e só é necessária no caso de um iterador ser inaceitável.

Resultados:

reversed(lst) -- 11.2 usecs

list(reversed(lst)) -- 37.1 usecs

lst[::-1] -- 23.6 usecs

Cálculos:

# 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

Conclusões:

A conclusão desses testes reversed()é mais rápida do que a fatia [::-1]em 12,4 usecs

mekarpeles
fonte
15
reversed () retorna um objeto iterador que é avaliado lentamente, então eu acho que não é uma comparação justa com a notação de fatiamento [:: - 1] em geral.
iridescente de
1
Mesmo em um caso em que um iterador pode ser usado diretamente, como ''.join(reversed(['1','2','3'])), o método de fatia ainda é> 30% mais rápido.
dansalmo
1
Não é à toa que você obteve o mesmo resultado nos 2 primeiros testes: eles são idênticos!
MestreLion
Meus resultados se assemelham mais a isso. ol [:: - 1] leva cerca de duas vezes mais do que list (reversed (ol)). O método ol [:: - 1] leva menos caracteres para escrever. No entanto, list (reversed (ol)) é provavelmente mais legível para programadores python iniciantes e é mais rápido na minha máquina.
dhj