Maneira mais limpa de obter o último item do iterador Python

110

Qual é a melhor maneira de obter o último item de um iterador no Python 2.6? Por exemplo, diga

my_iter = iter(range(5))

Qual é o caminho mais curto-code / mais limpa de obter 4a partir de my_iter?

Eu poderia fazer isso, mas não parece muito eficiente:

[x for x in my_iter][-1]
Peter
fonte
4
Os iteradores presumem que você deseja iterar através dos elementos e não acessar os últimos elementos. O que o impede de simplesmente usar range (5) [- 1]?
Frank
7
@Frank - Presumi que o iterador real era mais complexo e / ou mais distante e / ou mais difícil de controlar do queiter(range(5))
Chris Lutz
3
@Frank: o fato de que é na verdade uma função geradora muito mais complicada que fornece o iterador. Eu apenas inventei este exemplo para que fosse simples e claro o que estava acontecendo.
Peter
4
Se você quiser o último item de um iterador, há uma grande chance de estar fazendo algo errado. Mas a resposta é que não existe uma maneira mais limpa de iterar por meio do iterador. Isso ocorre porque os iteradores não têm um tamanho e, na verdade, podem nunca terminar e, como tal, podem não ter um último item. (Isso significa que seu código será executado para sempre, é claro). Portanto, a pergunta persistente é: por que você deseja o último item de um iterador?
Lennart Regebro
3
@Peter: Atualize sua pergunta, por favor. Não adicione muitos comentários a uma pergunta sua. Por favor, atualize a pergunta e remova os comentários.
S.Lott

Respostas:

100
item = defaultvalue
for item in my_iter:
    pass
Thomas Wouters
fonte
4
Por que o espaço reservado "valor padrão"? Porque não None? É exatamente para isso que Noneserve. Você está sugerindo que algum valor padrão específico da função pode até estar correto? Se o iterador não iterar de fato, um valor fora da banda é mais significativo do que algum padrão enganoso específico de função.
S.Lott
46
O valor padrão é apenas um espaço reservado para meu exemplo. Se você deseja usar Nonecomo valor padrão, é sua escolha. Nenhum nem sempre é o padrão mais sensato e pode nem estar fora da banda. Pessoalmente, tendo a usar 'defaultvalue = object ()' para ter certeza de que é um valor verdadeiramente único. Estou apenas indicando que a escolha do padrão está fora do escopo deste exemplo.
Thomas Wouters
28
@ S.Lott: talvez seja útil distinguir a diferença entre um iterador vazio e um iterador que tem Nonecomo valor final
John La Rooy
8
Há um erro de design em todos os iteradores de todos os tipos de contêineres integrados? É a primeira vez que ouço falar nisso :)
Thomas Wouters
7
Embora esta seja provavelmente a solução mais rápida, ela depende da variável vazando em loops for (um recurso para alguns, um bug para outros - provavelmente os caras do FP estão horrorizados). De qualquer forma, Guido disse que isso sempre funcionará assim, então é uma construção segura de usar.
tokland
68

Use um dequede tamanho 1.

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
martin23487234
fonte
6
Na verdade, essa é a maneira mais rápida de exaurir uma sequência longa, embora apenas um pouco mais rápida do que o loop for.
Sven Marnach
11
+1 por ser tecnicamente correto, mas os leitores devem ter as advertências usuais do Python, "Você REALMENTE precisa otimizar isso?", "Isso é menos explícito, o que não é Pythônico" e "A velocidade mais rápida depende da implementação, que pode mudar."
leewz de
1
também, é um
devorador de
6
@EelcoHoogendoorn Por que é um devorador de memória, mesmo com um maxlen de 1?
Chris Wesseling
1
De todas as soluções apresentadas aqui até agora, considero esta a mais rápida e a mais eficiente em termos de memória .
Markus Strauss,
66

Se você estiver usando Python 3.x:

*_, last = iterator # for a better understanding check PEP 448
print(last)

se você estiver usando o python 2.7:

last = next(iterator)
for last in iterator:
    continue
print last


Nota:

Normalmente, a solução apresentada acima é o que você precisa para casos regulares, mas se você está lidando com uma grande quantidade de dados, é mais eficiente usar um dequetamanho 1. ( fonte )

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
DhiaTN
fonte
1
@virtualxtc: O sublinhado é apenas um identificador. A estrela na frente diz "expanda a lista". O mais legível seria *lst, last = some_iterable.
pepr
4
@virtualxtc nope _é uma variável especial em python e usada para armazenar o último valor ou para dizer que não me importo com o valor, então pode ser limpo.
DhiaTN
1
Essa solução Python 3 não é eficiente em termos de memória.
Markus Strauss,
3
@DhiaTN Sim, você está absolutamente certo. Na verdade, eu gosto muito do idioma Python 3 que você mostrou. Só queria deixar claro que não funciona para "big data". Eu uso coleções.deque para isso, que passa a ser rápido e eficiente em termos de memória (consulte a solução de martin23487234).
Markus Strauss
1
Este exemplo py3.5 + deve estar no PEP 448. Maravilhoso.
EliadL de
33

Provavelmente vale a pena usar __reversed__se estiver disponível

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass
John La Rooy
fonte
27

Tão simples quanto:

max(enumerate(the_iter))[1]
Chema Cortes
fonte
8
Oh, isso é inteligente. Não é o mais eficiente ou legível, mas inteligente.
timgeb
6
Então, pensando em voz alta ... Isso funciona porque enumerateretorna (index, value)como: (0, val0), (1, val1), (2, val2)... e então por padrão maxquando dada uma lista de tuplas, compara apenas com o primeiro valor da tupla, a menos que os dois primeiros valores sejam iguais, o que eles nunca estão aqui porque eles representam índices. Então o subscrito final é porque max retorna a tupla inteira (idx, valor) enquanto estamos apenas interessados ​​em value. Idéia interessante.
Taylor Edmiston
21

É improvável que seja mais rápido do que o loop for vazio devido ao lambda, mas talvez dê a outra pessoa uma ideia

reduce(lambda x,y:y,my_iter)

Se o iter estiver vazio, um TypeError é gerado

John La Rooy
fonte
IMHO, este é o mais direto, conceitualmente. Em vez de aumentar TypeErrorpara um iterável vazio, você também pode fornecer um valor padrão por meio do valor inicial de reduce(), por exemplo last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default),.
egnha
9

Tem isso

list( the_iter )[-1]

Se a duração da iteração for verdadeiramente épica - tão longa que materializar a lista irá exaurir a memória - então você realmente precisa repensar o design.

S.Lott
fonte
1
Esta é a solução mais direta.
laike9m
2
Um pouco melhor usar uma tupla.
Christopher Smith
9
Discordo totalmente com a última frase. Trabalhar com conjuntos de dados muito grandes (que podem exceder os limites da memória se carregados de uma vez) é o principal motivo para usar um iterador em vez de uma lista.
Paul,
@Paul: algumas funções retornam apenas um iterador. Essa é uma maneira curta e bem legível de fazer isso nesse caso (para listas não épicas).
serv-inc
Essa é a maneira menos eficiente que se deve evitar como um mau mau mau hábito. Outra é usar sort (sequence) [- 1] para obter o elemento max da seqüência. Nunca use esses padrões inadequados se você gosta de ser engenheiro de software.
Maksym Ganenko
5

eu usaria reversed , exceto que leva apenas sequências em vez de iteradores, o que parece bastante arbitrário.

De qualquer maneira, você terá que percorrer todo o iterador. Com eficiência máxima, se você não precisar do iterador nunca mais, pode simplesmente destruir todos os valores:

for last in my_iter:
    pass
# last is now the last item

Eu acho que esta é uma solução abaixo do ideal, no entanto.

Chris Lutz
fonte
4
reversed () não leva um iterador, apenas sequências.
Thomas Wouters
3
Não é nada arbitrário. A única maneira de reverter um iterador é iterar até o fim, enquanto mantém todos os itens na memória. Eu, e, você precisa primeiro fazer uma sequência disso, antes de poder revertê-la. O que, obviamente, anula o propósito do iterador em primeiro lugar, e também significaria que você usaria muita memória de repente sem motivo aparente. Portanto, é o oposto de arbitrário, na verdade. :)
Lennart Regebro
@Lennart - Quando eu disse arbitrário, quis dizer irritante. Estou concentrando minhas habilidades no idioma no meu trabalho que será entregue em algumas horas a esta hora da manhã.
Chris Lutz
3
Justo. Embora seja IMO, seria mais chato se aceitasse iteradores, porque quase qualquer uso dele seria uma má ideia (tm). :)
Lennart Regebro
3

A biblioteca toolz oferece uma boa solução:

from toolz.itertoolz import last
last(values)

Mas adicionar uma dependência não principal pode não valer a pena para usá-la apenas neste caso.

lumbric
fonte
0

Eu apenas usaria next(reversed(myiter))

thomas.mac
fonte
8
TypeError: o argumento para reversed () deve ser uma sequência
Labo
0

A questão é sobre como obter o último elemento de um iterador, mas se o seu iterador for criado aplicando condições a uma sequência, então reverso pode ser usado para encontrar o "primeiro" de uma sequência reversa, olhando apenas para os elementos necessários, aplicando reverter para a própria sequência.

Um exemplo inventado,

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8
Wyrmwood
fonte
0

Como alternativa para iteradores infinitos, você pode usar:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

Achei que seria mais lento, dequemas é tão rápido e, na verdade, mais rápido do que o método de loop (de alguma forma)

qocu
fonte
-6

A pergunta está errada e só pode levar a uma resposta complicada e ineficiente. Para obter um iterador, é claro que você começa a partir de algo que é iterável, que na maioria dos casos oferece uma maneira mais direta de acessar o último elemento.

Depois de criar um iterador a partir de um iterável, você fica preso em percorrer os elementos, porque essa é a única coisa que um iterável fornece.

Portanto, a maneira mais eficiente e clara não é criar o iterador em primeiro lugar, mas usar os métodos de acesso nativos do iterável.

Ludwig
fonte
5
Então, como você obteria a última linha de um arquivo?
Brice M. Dempsey
@ BriceM.Dempsey A melhor maneira não é iterar por todo o arquivo (talvez enorme), mas ir para o tamanho do arquivo menos 100, ler os últimos 100 bytes, procurar por uma nova linha neles, se não houver, vá para trás outros 100 bytes, etc. Você também pode aumentar o tamanho do recuo, dependendo do seu cenário. Ler um zilhão de linhas definitivamente não é uma solução ideal.
Alfe