Quelle est la manière pythonique de détecter le dernier élément dans une boucle «for»?

188

J'aimerais connaître la meilleure façon (façon plus compacte et "pythonique") de faire un traitement spécial pour le dernier élément d'une boucle for. Il y a un morceau de code qui ne devrait être appelé qu'entre éléments, étant supprimé dans le dernier.

Voici comment je le fais actuellement:

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

Y a-t-il une meilleure façon?

Remarque: je ne veux pas le faire avec des hacks tels que l'utilisation reduce.;)

e.tadeu
la source
Et le premier? Doit-il être supprimé aussi?
Adam Matan
pouvez-vous nous dire ce qui se fait entre les éléments?
SilentGhost
2
J'aimerais avoir la réponse pour un cas générique, mais un cas concret où j'en ai besoin est d'écrire des choses sur un flux, avec des séparateurs entre eux, tout comme stream.write (',' .join (name_list)), mais le faire dans une boucle for sans concaténer les chaînes, car il y a beaucoup d'écritures ...
e.tadeu
Les trois premières lignes de cette réponse m'ont vraiment aidé, c'était d'avoir un défi similaire.
cardamome

Réponses:

151

La plupart du temps, il est plus facile (et moins coûteux) de faire de la première itération le cas particulier au lieu du dernier:

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

    item()

Cela fonctionnera pour tout itérable, même pour ceux qui n'ont pas len():

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

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

En dehors de cela, je ne pense pas qu'il existe une solution généralement supérieure car cela dépend de ce que vous essayez de faire. Par exemple, si vous construisez une chaîne à partir d'une liste, il est naturellement préférable d'utiliser str.join()une forboucle «avec cas particulier».


En utilisant le même principe mais plus compact:

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

Cela semble familier, n'est-ce pas? :)


Pour @ofko, et d'autres qui ont vraiment besoin de savoir si la valeur actuelle d'un itérable sans len()est la dernière, vous devrez regarder vers l'avenir:

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

Ensuite, vous pouvez l'utiliser comme ceci:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False
Ferdinand Beyer
la source
1
Certes, cette façon semble meilleure que la mienne, au moins elle n'a pas besoin d'utiliser enumerate et len.
e.tadeu
Oui, mais cela en ajoute un autre ifqui pourrait être évité si la boucle était divisée en deux boucles. Cependant, cela n'est pertinent que lors de l'itération d'une énorme liste de données.
Adam Matan
Le problème avec le fractionnement en deux boucles est qu'il viole DRY ou qu'il vous oblige à définir des méthodes.
e.tadeu
J'essaie vraiment de comprendre votre dernier exemple (qui fonctionne parfaitement dans mon code), mais je ne comprends pas comment cela fonctionne (l'idée derrière)
Olivier Pons
1
@OlivierPons Vous devez comprendre le protocole d'itérateur de Python: j'obtiens un itérateur pour un objet et récupère la première valeur avec next(). Ensuite, j'exploite qu'un itérateur est itérable par lui-même, donc je peux l'utiliser dans la forboucle jusqu'à épuisement, itération de la seconde à la dernière valeur. Pendant ce temps, je garde la valeur actuelle que j'ai récupérée de l'itérateur localement et yieldla dernière à la place. De cette façon, je sais qu'il y a encore une valeur à venir. Après la boucle for, j'ai signalé toutes les valeurs sauf la dernière.
Ferdinand Beyer
20

Bien que cette question soit assez ancienne, je suis venu ici via google et j'ai trouvé un moyen assez simple: le découpage de listes. Supposons que vous souhaitiez mettre un «&» entre toutes les entrées de la liste.

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

Cela renvoie «1 & 2 & 3».

BeckmaR
la source
7
Vous venez de réimplémenter la fonction de jointure: `" & ".join ([str (x) for x in l])
Bryan Oakley
la concaténation de chaînes est quelque peu inefficace. Si len(l)=1000000dans cet exemple, le programme fonctionnera pendant un certain temps. appendest recommandé afaik. l=[1,2,3]; l.append(4);
plhn
18

Le «code entre» est un exemple du modèle Head-Tail .

Vous avez un élément, qui est suivi d'une séquence de paires (entre, éléments). Vous pouvez également afficher cela sous la forme d'une séquence de paires (d'éléments, entre) suivies d'un élément. Il est généralement plus simple de prendre le premier élément comme spécial et tous les autres comme le cas «standard».

De plus, pour éviter de répéter du code, vous devez fournir une fonction ou un autre objet pour contenir le code que vous ne voulez pas répéter. Incorporer une instruction if dans une boucle qui est toujours fausse sauf une fois est un peu ridicule.

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 )

Ceci est plus fiable car c'est un peu plus facile à prouver, cela ne crée pas de structure de données supplémentaire (c'est-à-dire une copie d'une liste) et ne nécessite pas beaucoup d'exécution gaspillée d'une condition if qui est toujours fausse sauf une fois.

S.Lott
la source
4
Les appels de fonction sont beaucoup plus lents que les ifinstructions, donc l'argument «exécution gaspillée» ne tient pas.
Ferdinand Beyer
1
Je ne sais pas ce que la différence de vitesse entre l'appel de fonction et l'instruction if a à voir avec quoi que ce soit. Le fait est que cette formulation n'a pas d'énoncé if qui est toujours faux (sauf une fois.)
S.Lott
1
J'ai interprété votre déclaration «… et ne nécessite pas beaucoup d'exécution gaspillée d'une condition if qui est toujours fausse sauf une fois» comme «… et est plus rapide car elle économise quelques ifsecondes». De toute évidence, vous faites simplement référence à la «propreté du code»?
Ferdinand Beyer
La définition d'une fonction au lieu d'utiliser une ifinstruction est-elle vraiment considérée comme plus propre par la communauté Python?
Markus von Broady
17

Si vous cherchez simplement à modifier le dernier élément, data_listvous pouvez simplement utiliser la notation:

L[-1]

Cependant, il semble que vous faites plus que cela. Il n'y a rien de vraiment mal avec votre chemin. J'ai même jeté un coup d'œil rapide sur du code Django pour leurs balises de modèle et ils font essentiellement ce que vous faites.

Bartek
la source
1
Je ne le modifie pas, je l'utilise pour faire quelque chose
e.tadeu
4
@ e.tadeu peu importe si vous le modifiez ou non. Changer votre déclaration if en: if data != datalist[-1]:et garder tout le reste pareil serait le meilleur moyen de coder cela à mon avis.
spacetyper
8
@spacetyper Ceci s'interrompt lorsque la dernière valeur n'est pas unique.
Ark-kun
14

si les éléments sont uniques:

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

autres options:

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
Palza
la source
10

Ceci est similaire à l'approche d'Ants Aasma mais sans utiliser le module itertools. C'est aussi un itérateur en retard qui anticipe un seul élément dans le flux d'itérateur:

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
Andrew Dalke
la source
4

Vous pouvez utiliser une fenêtre glissante sur les données d'entrée pour avoir un aperçu de la valeur suivante et utiliser une sentinelle pour détecter la dernière valeur. Cela fonctionne sur n'importe quel itérable, vous n'avez donc pas besoin de connaître la longueur au préalable. L'implémentation par paires provient des recettes 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
Fourmis Aasma
la source
3

N'y a-t-il pas de possibilité d'itérer sur tout sauf le dernier élément, et de traiter le dernier en dehors de la boucle? Après tout, une boucle est créée pour faire quelque chose de similaire à tous les éléments sur lesquels vous bouclez; si un élément a besoin de quelque chose de spécial, il ne devrait pas être dans la boucle.

(voir aussi cette question: est-ce-que-le-dernier-élément-dans-une-boucle-mérite-un-traitement-séparé )

EDIT: puisque la question porte davantage sur l '«entre», soit le premier élément est le spécial en ce qu'il n'a pas de prédécesseur, soit le dernier élément est spécial en ce qu'il n'a pas de successeur.

xtofl
la source
Mais le dernier élément doit être traité de la même manière que tous les autres éléments de la liste. Le problème est la chose qui ne devrait être faite qu'entre les éléments.
e.tadeu
Dans ce cas, le premier est le seul sans prédécesseur. Démontez-le et parcourez le reste du code général de la liste.
xtofl
3

J'aime l'approche de @ ethan-t, mais while Truec'est dangereux de mon point de vue.

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

Ici, data_listc'est pour que le dernier élément soit égal en valeur au premier de la liste. L peut être échangé avec data_listmais dans ce cas il est vide après la boucle. while Trueest également possible d'utiliser si vous vérifiez que la liste n'est pas vide avant le traitement ou si la vérification n'est pas nécessaire (aïe!).

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')

La bonne partie est que c'est rapide. Le mauvais - il est destructible ( data_listdevient vide).

Solution la plus intuitive:

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}')

Oh oui, vous l'avez déjà proposé!

Aliaksandr Klimovich
la source
2

Il n'y a rien de mal dans votre chemin, à moins que vous n'ayez 100 000 boucles et que vous vouliez enregistrer 100 000 instructions «si». Dans ce cas, vous pouvez procéder comme suit:

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

Les sorties :

1
2
3
3

Mais vraiment, dans votre cas, j'ai l'impression que c'est exagéré.

Dans tous les cas, vous aurez probablement plus de chance avec le tranchage:

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

#outputs
1
2
last : 3

ou juste :

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

#outputs
1
2
3
last : 3

Finalement, une façon KISS de faire vos choses, et cela fonctionnerait avec n'importe quel itérable, y compris ceux sans __len__:

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

Sorties:

1
2
3
3

Si j'ai l'impression que je le ferais de cette façon, cela me semble simple.

e-satis
la source
2
Mais notez que itérable [-1] ne fonctionnera pas avec tous les itérables (comme les générateurs qui n'ont pas de len )
e.tadeu
Si tout ce que vous voulez est d'accéder au dernier élément après la boucle, utilisez simplement itemau lieu de le recalculer avec list[-1]. Mais néanmoins: je ne pense pas que ce soit ce que le PO demandait, n'est-ce pas?
Ferdinand Beyer
Re: iterable.__iter__() - veuillez ne pas appeler les __fonctions directement. Devrait être iter(iterable).
PaulMcG
2

Utilisez le tranchage et ispour vérifier le dernier élément:

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>

Caveat emptor : Cela ne fonctionne que si tous les éléments de la liste sont réellement différents (ont des emplacements différents en mémoire). Sous le capot, Python peut détecter des éléments égaux et réutiliser les mêmes objets pour eux. Par exemple, pour des chaînes de même valeur et des entiers communs.

Roger Dahl
la source
2

si vous parcourez la liste, cela a aussi fonctionné pour moi:

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

Google m'a amené à cette vieille question et je pense que je pourrais ajouter une approche différente à ce problème.

La plupart des réponses ici concerneraient un traitement approprié d'un contrôle de boucle for comme cela a été demandé, mais si la data_list est destructible, je vous suggère de faire apparaître les éléments de la liste jusqu'à ce que vous vous retrouviez avec une liste vide:

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()

vous pouvez même utiliser while len (element_list) si vous n'avez rien à faire avec le dernier élément. Je trouve cette solution plus élégante que de traiter avec next ().

Anderson Santos
la source
2

Pour moi, le moyen le plus simple et le plus pythonique de gérer un cas particulier à la fin d'une liste est:

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

Bien entendu, cela peut également être utilisé pour traiter le premier élément d'une manière spéciale.

Chris
la source
2

Au lieu de compter, vous pouvez également compter à rebours:

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

Retardez le traitement spécial du dernier élément jusqu'après la boucle.

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

Il peut y avoir plusieurs façons. le tranchage sera le plus rapide. Ajout d'un autre qui utilise la méthode .index ():

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

En supposant que l'entrée soit un itérateur, voici un moyen d'utiliser tee et izip d'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.

Démo:

>>> 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
>>>
Anon
la source
0

La solution la plus simple qui me vient à l'esprit est:

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

Nous regardons donc toujours en avant un élément en retardant le traitement d'une itération. Pour ne pas faire quelque chose lors de la première itération, j'attrape simplement l'erreur.

Bien sûr, vous devez réfléchir un peu, pour que le NameErrorsoit élevé quand vous le souhaitez.

Gardez également le `counstruct

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

Cela suppose que le nom new n'a pas été défini auparavant. Si vous êtes paranoïaque, vous pouvez vous assurer que cela newn'existe pas en utilisant:

try:
    del new
except NameError:
    pass

Alternativement, vous pouvez bien sûr également utiliser une instruction if ( if notfirst: print(new) else: notfirst = True). Mais pour autant que je sache, les frais généraux sont plus importants.


Using `timeit` yields:

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

je m'attends donc à ce que les frais généraux ne soient pas sélectionnables.

DerWeh
la source
0

Comptez les articles une fois et suivez le nombre d'articles restants:

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

De cette façon, vous n'évaluez qu'une seule fois la longueur de la liste. La plupart des solutions de cette page semblent supposer que la longueur n'est pas disponible à l'avance, mais cela ne fait pas partie de votre question. Si vous avez la longueur, utilisez-la.

Ethan T
la source
0

Une solution simple qui me vient à l'esprit serait:

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

Une deuxième solution tout aussi simple pourrait être obtenue en utilisant un compteur:

# 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
Tout est beau
la source