Quelle est la façon pythonique de détecter le dernier élément d'une boucle "pour" python?

j'aimerais connaître la meilleure façon (plus compact et "pythonic") afin de faire un traitement spécial pour le dernier élément dans une boucle for. Il y a un morceau de code qui devrait être appelé seulement entre les éléments , étant supprimé dans le dernier.

, Voici comment je fais:

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 un meilleur moyen?

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

125
demandé sur lesmana 2009-10-27 14:54:33

18 réponses

la plupart du temps, il est plus facile (et moins cher) de faire le 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 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 y ait 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, c'est il est naturellement préférable d'utiliser str.join() plutôt que for boucle "avec cas particulier".


utilisant le même principe mais plus compact:

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

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


Pour @ofko, et d'autres qui en ont vraiment besoin pour savoir si la valeur actuelle d'un objet iterable sans len() est le dernier, vous aurez besoin de 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

, Alors vous pouvez l'utiliser comme ceci:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False
112
répondu Ferdinand Beyer 2015-12-06 21:00:06

le" code entre "est un exemple de Tête-Queue .

vous avez un article, qui est suivi d'une séquence de ( entre, article ) paires. Vous pouvez également voir ceci comme une séquence de (élément, entre) paires suivie d'un élément. Il est généralement plus simple de prendre le premier élément spécial et tous les autres comme la "norme".

en outre, pour éviter de répéter le code, vous devez fournir une fonction ou autre objet doit contenir le code que vous ne voulez pas de répéter. Intégrer une instruction si dans une boucle qui est toujours fausse sauf une fois est un peu idiot.

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 )

c'est plus fiable parce que c'est légèrement plus facile à prouver, il ne crée pas une structure de données supplémentaire (i.e., une copie d'une liste) et ne nécessite pas beaucoup d'exécution gaspillée d'une condition si qui est toujours fausse sauf une fois.

17
répondu S.Lott 2009-10-27 13:38:36

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

L[-1]

cependant, il semble que vous faites plus que cela. Il n'y a rien de mal à votre façon de faire. J'ai même jeté un coup d'oeil à certains code Django pour leurs étiquettes de modèle et ils font essentiellement ce que vous faites.

13
répondu Bartek 2009-10-27 12:05:19

c'est similaire à L'approche de L'Aasma, mais sans utiliser le module itertools. C'est aussi un itérateur à retardement qui regarde vers l'avant un élément unique dans le flux 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
9
répondu Andrew Dalke 2009-10-27 21:45:15

bien que cette question soit assez ancienne, je suis venu ici par google et j'ai trouvé un moyen assez simple: liste tranchant. Disons que vous voulez faire 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'.

8
répondu Coffeinated 2015-06-17 07:46:09

Vous pouvez utiliser une fenêtre coulissante sur les données d'entrée pour obtenir un coup d'oeil à la prochaine valeur et de l'utilisation d'une sentinelle pour détecter la dernière valeur. Cela fonctionne sur n'importe quel itérable, de sorte que vous n'avez pas besoin de connaître la longueur d'avance. L'implémentation par paires est de itertools recipes .

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
répondu Ants Aasma 2009-10-27 13:17:21

N'y a-t-il aucune possibilité d'itérer 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 que 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: n'-le-dernier-élément-dans-une-boucle-méritent-un-séparée-traitement )

EDIT: puisque la question est plus sur le "en entre", soit le première élément est spécial dans la mesure où elle n'a pas de prédécesseur, ou le dernier élément est particulier en ce qu'il n'a pas de successeur.

3
répondu xtofl 2017-05-23 11:47:31

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
3
répondu Palza 2016-11-07 00:16:02

utilisez tranchage et is pour 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 dans la 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 la même valeur et des entiers communs.

2
répondu Roger Dahl 2015-06-28 17:41:17

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 traiteraient d'un traitement approprié d'un contrôle de boucle comme il a été demandé, mais si la data_list est destructible, je suggérerais que vous pop les éléments de la liste jusqu'à ce que vous finissiez 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 alors que len (element_list) si vous n'avez pas besoin rien à faire avec le dernier élément. Je trouve cette solution plus élégante que de traiter avec next().

2
répondu Anderson Santos 2016-07-14 14:09:10

il n'y a rien de mal dans votre façon de faire, à moins que vous ayez 100 000 boucles et que vous vouliez sauver 100 000" si". Dans ce cas, vous pouvez aller par là :

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

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 simplement :

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

#outputs
1
2
3
last : 3

eventuellement, une façon de baiser pour vous faire des trucs, et qui 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 j'ai l'impression que je le ferais de cette façon, cela me semble simple.

1
répondu e-satis 2009-10-28 11:56:24

retarder la manipulation spéciale du dernier article jusqu'après la boucle.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3
1
répondu user240515 2017-08-01 22:54:36

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
répondu Aliaksandr Klimovich 2018-03-26 07:48:25

en supposant que input est un itérateur, voici un moyen d'utiliser tee et izip à partir des itértools:

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
répondu Anon 2009-10-27 13:39:40

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

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()
0
répondu tsf144 2016-01-08 21:51:37

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

donc nous regardons toujours devant un élément en retardant le traitement 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 avez besoin de réfléchir un peu, afin que le NameError d'être soulevé quand vous le voulez.

garde aussi le Conseil

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

cela signifie que la 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 bien sûr aussi 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

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

0
répondu DerWeh 2017-04-03 13:45:23

compter les articles une fois et suivre 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

ainsi, vous n'évaluez la longueur de la liste qu'une seule fois. Beaucoup de solutions sur 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.

0
répondu Ethan T 2017-05-23 20:04:20

pour moi la façon la plus simple et pythonique de gérer un cas spécial à la fin d'une liste est:

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 d'une manière spéciale .

0
répondu Chris 2018-07-19 11:55:48