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
;)
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
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.
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.
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
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'.
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
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.
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
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.
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().
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.
retarder la manipulation spéciale du dernier article jusqu'après la boucle.
>>> for i in (1, 2, 3):
... pass
...
>>> i
3
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.')
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
>>>
si vous parcourez la liste, pour moi cela a fonctionné aussi:
for j in range(0, len(Array)):
if len(Array) - j > 1:
notLast()
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.
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.
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 .