Qual é a maneira pitônica de detectar o último elemento em um loop 'for'?


188

Gostaria de saber a melhor maneira (mais compacta e "pitônica") de fazer um tratamento especial para o último elemento em um loop for. Há um pedaço de código que deve ser chamado apenas entre os elementos, sendo suprimido no último.

Aqui está como eu faço atualmente:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

Existe alguma maneira melhor?

Nota: Eu não quero fazer isso com hacks, como usar reduce.;)


E o primeiro? Também deve ser suprimido?
27511 Adam Matan

você poderia nos dizer o que está sendo feito entre os elementos?
SilentGhost 27/10/2009

2
Gostaria de obter a resposta para um caso genérico, mas um caso concreto em que preciso disso é escrever coisas em um fluxo, com separadores entre eles, como stream.write (',' .join (name_list)), mas fazê-lo em um loop sem concatenar as cordas, porque há muitas gravações ...
e.tadeu


As três primeiras linhas desta resposta realmente me ajudaram, estava tendo um desafio semelhante.
cardamomo

Respostas:


151

Na maioria das vezes, é mais fácil (e mais barato) tornar a primeira iteração o caso especial, em vez da última:

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

Isso funcionará para qualquer iterável, mesmo para aqueles que não possuem len():

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

Além disso, não acho que exista uma solução geralmente superior, pois depende do que você está tentando fazer. Por exemplo, se você estiver construindo uma string a partir de uma lista, é naturalmente melhor usar do str.join()que usar um forloop "com caso especial".


Usando o mesmo princípio, mas mais compacto:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

Parece familiar, não é? :)


Para o @ofko e outros que realmente precisam descobrir se o valor atual de um iterável sem len()é o último, você precisará olhar para o futuro:

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

Então você pode usá-lo assim:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

1
É verdade que esse caminho parece melhor que o meu, pelo menos não é necessário usar enumerar e len.
e.tadeu 27/10/09

Sim, mas adiciona outro ifque poderia ser evitado se o loop fosse dividido em dois loops. No entanto, isso é relevante apenas ao iterar uma enorme lista de dados.
23811 Adam Matan

O problema de dividir em dois loops é que ele viola o DRY ou obriga a definir métodos.
e.tadeu 27/10/2009

Eu realmente tentar entender o seu último exemplo (que funciona perfeitamente no meu código), mas eu não entendo como ele funciona (a idéia por trás)
Olivier Pons

1
@OlivierPons Você precisa entender o protocolo iterador do Python: eu recebo um iterador para um objeto e recupero o primeiro valor com next(). Exploro então que um iterador é iterável por si só, para que eu possa usá-lo no forloop até a exaustão, iteração do segundo ao último valor. Durante isso, mantenho o valor atual que recuperei do iterador localmente e yieldo último. Dessa forma, sei que há mais um valor por vir. Após o loop for, relatei todos os valores, exceto o último.
Ferdinand Beyer

20

Embora essa pergunta seja bastante antiga, eu vim aqui pelo google e achei uma maneira bastante simples: fatiar a lista. Digamos que você queira colocar um '&' entre todas as entradas da lista.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

Isso retorna '1 e 2 e 3'.


7
Você acabou de reimplementar a função de junção: `" & ".join ([str (x) para x em l])"
Bryan Oakley

a concatenação de strings é um tanto ineficiente. Se len(l)=1000000, neste exemplo, o programa for executado por um tempo. appendé recomendado afaik. l=[1,2,3]; l.append(4);
plhn

18

O 'código entre' é um exemplo do padrão Head-Tail .

Você tem um item, que é seguido por uma sequência de pares (entre itens). Você também pode ver isso como uma sequência de (item, entre) pares seguidos por um item. Geralmente é mais simples considerar o primeiro elemento como especial e todos os outros como o caso "padrão".

Além disso, para evitar a repetição de código, você deve fornecer uma função ou outro objeto para conter o código que não deseja repetir. A incorporação de uma instrução if em um loop que é sempre falsa, exceto que uma vez é meio boba.

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = head_tail_iter.next()
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

Isso é mais confiável, porque é um pouco mais fácil de provar. Ele não cria uma estrutura de dados extra (ou seja, uma cópia de uma lista) e não requer muita execução desperdiçada de uma condição if que é sempre falsa, exceto uma vez.


4
As chamadas de função são muito mais lentas do que as ifinstruções, para que o argumento "execução desperdiçada" não seja válido.
28830 Ferdinand Beyer

1
Não sei ao certo qual a diferença de velocidade entre a chamada de função e a instrução if. O ponto é que esta formulação tem instrução se-não é sempre falsa (exceto uma vez.)
S. Lott

1
Interpretei sua afirmação “… e não requer muita execução desperdiçada de uma condição if que é sempre falsa, exceto uma vez” como “… e é mais rápida, pois economiza alguns ifs”. Obviamente você está apenas se referindo à "limpeza de código"?
Ferdinand Beyer

Definir uma função em vez de usar uma ifdeclaração é realmente considerado mais limpo pela comunidade Python?
Markus von Broady

17

Se você está simplesmente querendo modificar o último elemento data_list, pode simplesmente usar a notação:

L[-1]

No entanto, parece que você está fazendo mais do que isso. Não há nada realmente errado no seu caminho. Eu até dei uma rápida olhada em algum código do Django para suas tags de modelo e eles fazem basicamente o que você está fazendo.


1
Eu não estou modificando-o, eu estou usando-o para fazer alguma coisa
e.tadeu

4
@ e.tadeu nem importa se você está modificando ou não. Alterar sua instrução if para: if data != datalist[-1]:e manter todo o resto igual seria a melhor maneira de codificar isso na minha opinião.
Spacetyper 28/08/2015

8
@spacetyper É interrompido quando o último valor não é exclusivo.
Arca-kun

14

se os itens forem exclusivos:

for x in list:
    #code
    if x == list[-1]:
        #code

outras opções:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code

10

Isso é semelhante à abordagem do Ants Aasma, mas sem o uso do módulo itertools. Também é um iterador atrasado que antecipa um único elemento no fluxo do iterador:

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item

4

Você pode usar uma janela deslizante sobre os dados de entrada para obter uma espiada no próximo valor e usar um sentinela para detectar o último valor. Isso funciona em qualquer iterável, portanto você não precisa saber o tamanho de antemão. A implementação em pares é das receitas do itertools .

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item

3

Não há possibilidade de iterar sobre tudo, exceto o último elemento, e tratar o último fora do loop? Afinal, um loop é criado para fazer algo semelhante a todos os elementos em que você faz o loop; se um elemento precisa de algo especial, não deve estar no loop.

(veja também esta pergunta: o último elemento em um loop merece um tratamento separado )

EDIT: como a pergunta é mais sobre o "intermediário", o primeiro elemento é o especial por não ter predecessor ou o último elemento é especial por não ter sucessor.


Mas o último elemento deve ser tratado de maneira semelhante a todos os outros elementos da lista. O problema é o que deve ser feito apenas entre os elementos.
e.tadeu 27/10/09

Nesse caso, o primeiro é o único sem antecessor. Desmonte esse e faça um loop sobre o restante do código geral da lista.
Xtofl 27/10/09

3

Eu gosto da abordagem do @ ethan-t, mas while Trueé perigoso do meu ponto de vista.

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

Aqui, data_listé para que o último elemento seja igual em valor ao primeiro da lista. L pode ser trocado, data_listmas, neste caso, resulta vazio após o loop. while Truetambém é possível usar se você verificar se a lista não está vazia antes do processamento ou se a verificação não é necessária (ai!).

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

A parte boa é que é rápido. O ruim - é destrutível ( data_listfica vazio).

Solução mais intuitiva:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

Ah, sim, você já propôs!


2

Não há nada de errado com o seu caminho, a menos que você tenha 100.000 loops e queira salvar 100.000 instruções "se". Nesse caso, você pode seguir esse caminho:

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

Saídas:

1
2
3
3

Mas realmente, no seu caso, sinto que é um exagero.

De qualquer forma, você provavelmente terá mais sorte com o fatiamento:

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

ou apenas :

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

Eventualmente, uma maneira do KISS de fazer coisas para você, e que funcionaria com qualquer iterável, incluindo aqueles sem __len__:

item = ''
for item in iterable :
    print item
print item

Ouputs:

1
2
3
3

Se parece que eu faria dessa maneira, parece simples para mim.


2
Mas nota que iterable [-1] não funciona para todos os iterables (como gerador que não têm len )
e.tadeu

Se tudo o que você deseja é acessar o último item após o loop, basta usar em itemvez de recalculá-lo usando list[-1]. Mas, no entanto: eu não acho que isso é o que o OP estava pedindo, era?
28830 Ferdinand Beyer

Re: iterable.__iter__() - por favor, não chame __funções diretamente. Deveria ser iter(iterable).
28909 PaulMcG

2

Use o fatiamento e ispara verificar o último elemento:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

Advertência : isso só funciona se todos os elementos da lista forem realmente diferentes (com locais diferentes na memória). Sob o capô, o Python pode detectar elementos iguais e reutilizar os mesmos objetos para eles. Por exemplo, para cadeias de caracteres do mesmo valor e números inteiros comuns.


2

se você está lendo a lista, para mim também funcionou:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

2

O Google me levou a essa pergunta antiga e acho que poderia adicionar uma abordagem diferente para esse problema.

A maioria das respostas aqui trataria de um tratamento adequado de um controle de loop for, conforme solicitado, mas se a data_list for destrutível, sugiro que você apareça os itens da lista até terminar com uma lista vazia:

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

você pode até usar len (element_list) se não precisar fazer nada com o último elemento. Acho essa solução mais elegante do que lidar com next ().


2

Para mim, a maneira mais simples e pitônica de lidar com um caso especial no final de uma lista é:

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

Obviamente, isso também pode ser usado para tratar o primeiro elemento de uma maneira especial.


2

Em vez de contar, você também pode fazer uma contagem regressiva:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()

1

Atrasar o tratamento especial do último item até depois do loop.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

1

Pode haver várias maneiras. fatiar será mais rápido. Adicionando mais um que usa o método .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]

0

Assumindo a entrada como um iterador, aqui está uma maneira de usar tee e izip do itertools:

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

Demo:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>

0

A solução mais simples que me vem à cabeça é:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

Portanto, sempre observamos um item adiando a iteração de processamento. Para pular algo durante a primeira iteração, simplesmente pego o erro.

É claro que você precisa pensar um pouco, a fim de NameErroraumentar quando quiser.

Mantenha também o `` counstruct

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

Isso depende que o nome novo não foi definido anteriormente. Se você é paranóico, pode garantir que newisso não exista usando:

try:
    del new
except NameError:
    pass

Como alternativa, é claro que você também pode usar uma instrução if ( if notfirst: print(new) else: notfirst = True). Mas, tanto quanto eu sei, a sobrecarga é maior.


Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

então, espero que a sobrecarga seja inelegível.


0

Conte os itens uma vez e acompanhe o número de itens restantes:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

Dessa forma, você avalia apenas o comprimento da lista uma vez. Muitas das soluções nesta página parecem assumir que o tamanho não está disponível antecipadamente, mas isso não faz parte da sua pergunta. Se você tem o comprimento, use-o.


0

Uma solução simples que vem à mente seria:

for i in MyList:
    # Check if 'i' is the last element in the list
    if i == MyList[-1]:
        # Do something different for the last
    else:
        # Do something for all other elements

Uma segunda solução igualmente simples pode ser alcançada usando um contador:

# Count the no. of elements in the list
ListLength = len(MyList)
# Initialize a counter
count = 0

for i in MyList:
    # increment counter
    count += 1
    # Check if 'i' is the last element in the list
    # by using the counter
    if count == ListLength:
        # Do something different for the last
    else:
        # Do something for all other elements
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.