web-dev-qa-db-fra.com

Quel est le moyen utilisé par Pythonic pour détecter le dernier élément d'une boucle 'for' de python?

Je voudrais savoir la meilleure 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 devrait être appelé seulement 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?

Note: Je ne veux pas le faire avec des hacks comme utiliser reduce;)

135
e.tadeu

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

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

    item()

Cela fonctionnera pour tout type de valeur, même pour ceux qui n’ont pas de 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 vaut naturellement mieux utiliser str.join() que d'utiliser une boucle for «avec un cas spécial».


Utilisant le même principe mais plus compact:

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

Ça a l'air familier, n'est-ce pas? :)


Pour @ofko et les autres qui ont réellement besoin de savoir si la valeur actuelle d'un itérable sans len() est la dernière, vous devez regarder de l'avant:

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
119
Ferdinand Beyer

Le 'code entre' est un exemple du motif Head-Tail .

Vous avez un élément qui est suivi d'une séquence de paires (entre éléments). Vous pouvez également voir cela comme une séquence de paires (élément, 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 le code, vous devez fournir une fonction ou un autre objet contenant le code que vous ne souhaitez pas répéter. L'incorporation d'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 n'exige pas beaucoup d'exécution perdue d'une condition si qui est toujours faux sauf une fois.

18
S.Lott

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

L[-1]

Cependant, il semble que vous fassiez plus que cela. Il n'y a rien de mal à votre façon. J'ai même jeté un coup d'œil rapide sur le code Django pour leurs balises de gabarit et ils font essentiellement ce que vous faites.

13
Bartek

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

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
9
Andrew Dalke

si les articles 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
8
Palza

Bien que cette question soit assez ancienne, je suis arrivée ici via Google et j’ai trouvé un moyen assez simple: découper des 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])

Ceci retourne '1 & 2 & 3'.

8
Coffeinated

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 toutes les valeurs, donc vous n'avez pas besoin de connaître la longueur à l'avance. L'implémentation par paire provient de recettes d'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
4
Ants Aasma

N'y a-t-il pas possibilité de parcourir 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 survolés; si un élément a besoin de quelque chose de spécial, il ne devrait pas être dans la boucle.

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

EDIT: étant donné que la question porte davantage sur "entre les deux", l'élément first est l'élément spécial en ce sens qu'il n'a pas de prédécesseur, ou l'élément last est spécial en ce qu'il n'a pas de successeur.

3
xtofl

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 traitent du traitement approprié d'un contrôle de boucle for tel qu'il a été demandé, mais si la liste_données est destructible, je vous suggérerais de supprimer les éléments de la liste jusqu'à obtenir 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 (liste_éléments) si vous n'avez rien à faire avec le dernier élément. Je trouve cette solution plus élégante que de traiter avec next ().

2
Anderson Santos

Utilisez slicing et is pour rechercher 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 : Ceci 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 les chaînes de même valeur et les entiers communs.

2
Roger Dahl

Il n’ya rien de mal avec votre chemin, à moins d’avoir 100 000 boucles et de sauvegarder 100 000 déclarations "if". Dans ce cas, vous pouvez suivre cette voie:

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 découpage en tranches:

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, un KISS moyen de faire votre travail, et cela fonctionnerait avec n’importe quel itérable, y compris ceux sans __len__:

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

Ouputs:

1
2
3
3

Si je sens que je le ferais de cette façon, cela me semble simple.

2
e-satis

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

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')
1

Reportez le traitement spécial du dernier élément à la fin de la boucle.

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

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

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

Il peut y avoir plusieurs façons. le découpage 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]
0
user2857610

Au lieu de compter, vous pouvez aussi compter:

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

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

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

Bien sûr, cela peut également être utilisé pour traiter le premier élément de manière particulière.

0
Chris

En supposant une entrée en tant qu'itérateur, voici une façon d'utiliser tee et izip depuis 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
>>>
0
Anon

La solution la plus simple qui me vienne à l’esprit est la suivante:

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

Nous anticipons donc toujours un élément en retardant le traitement d’une itération. Pour éviter de 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 la variable NameError soit élevée quand vous le souhaitez.

Gardez aussi le `` ``

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

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

try:
    del new
except NameError:
    pass

Vous pouvez é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. 

0
DerWeh

Comptez les objets une fois et suivez le nombre d’items 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 fois la longueur de la liste. Beaucoup de solutions sur cette page semblent supposer que la longueur est indisponible à l'avance, mais cela ne fait pas partie de votre question. Si vous avez la longueur, utilisez-la.

0
Ethan T