¿Cuál es la forma pythonica de detectar el último elemento en un bucle ‘for’ de python?

Me gustaría saber la mejor manera (una forma más compacta y “pythonic”) de hacer un tratamiento especial para el último elemento en un bucle for. Hay un fragmento de código que debe llamarse solo entre elementos, que se suprime en el último.

Así es como lo hago actualmente:

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 

¿Hay alguna forma mejor?

Nota: no quiero hacerlo con hacks como el uso de reduce 😉

La mayoría de las veces es más fácil (y barato) hacer que la primera iteración sea un caso especial en lugar de la última:

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

Esto funcionará para cualquier iterable, incluso para aquellos que no tienen len() :

 file = open('/path/to/file') for line in file: process_line(line) # No way of telling if this is the last line! 

Aparte de eso, no creo que haya una solución generalmente superior, ya que depende de lo que estés tratando de hacer. Por ejemplo, si está creando una cadena a partir de una lista, es naturalmente mejor usar str.join() que usar un bucle for “con un caso especial”.


Usando el mismo principio pero más compacto:

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

Parece familiar, ¿no? 🙂


Para @ofko y otros que realmente necesitan descubrir si el valor actual de un iterable sin len() es el último, deberá mirar hacia adelante:

 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 

Entonces puedes usarlo así:

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

El ‘código entre’ es un ejemplo del patrón Head-Tail .

Tiene un elemento, seguido de una secuencia de pares (entre, elemento). También puede ver esto como una secuencia de (elemento, entre) pares seguidos por un elemento. En general, es más sencillo tomar el primer elemento como especial y todos los demás como el caso “estándar”.

Además, para evitar repetir el código, debe proporcionar una función u otro objeto que contenga el código que no desea repetir. Incrustar una instrucción if en un bucle que siempre es falso, excepto una vez, es algo tonto.

 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 ) 

Esto es más confiable porque es un poco más fácil de probar, no crea una estructura de datos adicional (es decir, una copia de una lista) y no requiere una gran cantidad de ejecución desperdiciada de una condición if que siempre es falsa, excepto una vez.

Si simplemente está buscando modificar el último elemento en la data_list de data_list , puede usar la notación:

 L[-1] 

Sin embargo, parece que estás haciendo más que eso. No hay nada realmente malo en tu camino. Incluso eché un vistazo rápido a algunos códigos Django para sus tags de plantilla y ellos hacen básicamente lo que estás haciendo.

Esto es similar al enfoque de Ants Aasma pero sin usar el módulo itertools. También es un iterador rezagado que mira hacia adelante un solo elemento en la secuencia del 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 

Aunque esa pregunta es bastante antigua, vine aquí a través de Google y encontré una forma bastante simple: Listar el corte. Digamos que desea colocar un ‘&’ entre todas las entradas de la lista.

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

Esto devuelve ‘1 & 2 & 3’.

Si los artículos son únicos:

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

otras opciones:

 pos = -1 for x in list: pos += 1 #code if pos == len(list) - 1: #code for x in list: #code #code - eg print x if len(list) > 0: for x in list[:-1] #code for x in list[-1]: #code 

Puede usar una ventana deslizante sobre los datos de entrada para echar un vistazo al siguiente valor y usar un centinela para detectar el último valor. Esto funciona en cualquier iterable, por lo que no necesita saber la longitud de antemano. La implementación por pares es de recetas de 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 

¿No hay posibilidad de iterar sobre todo, excepto el último elemento, y tratar el último fuera del bucle? Después de todo, se crea un bucle para hacer algo similar a todos los elementos sobre los que se recorre; Si un elemento necesita algo especial, no debería estar en el bucle.

(vea también esta pregunta: hace-el-último-elemento-en-un-bucle-merece-un-tratamiento-separado )

EDITAR: ya que la pregunta es más sobre el “en medio”, o el primer elemento es el especial porque no tiene predecesor, o el último elemento es especial porque no tiene sucesor.

No hay nada malo en tu camino, a menos que tengas 100 000 bucles y quieras guardar 100 000 declaraciones “if”. En ese caso, puedes ir por ese camino:

 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 

Salidas:

 1 2 3 3 

Pero realmente, en tu caso, siento que es una exageración.

En cualquier caso, probablemente tengas más suerte con el corte:

 for item in iterable[:-1] : print item print "last :", iterable[-1] #outputs 1 2 last : 3 

o solo :

 for item in iterable : print item print iterable[-1] #outputs 1 2 3 last : 3 

Eventualmente, una forma KISS de hacer tus cosas, y que funcionaría con cualquier iterable, incluyendo las que no tienen __len__ :

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

Ouputs:

 1 2 3 3 

Si siento que lo haría así, me parece simple.

Usa rebanar y verifica el último elemento:

 for data in data_list:  if not data is data_list[-1]:  

Caveat emptor : esto solo funciona si todos los elementos de la lista son realmente diferentes (tienen diferentes ubicaciones en la memoria). Bajo el capó, Python puede detectar elementos iguales y reutilizar los mismos objetos para ellos. Por ejemplo, para cadenas del mismo valor y enteros comunes.

Google me llevó a esta vieja pregunta y creo que podría agregar un enfoque diferente a este problema.

La mayoría de las respuestas aquí tratarán con un tratamiento adecuado de un control de bucle for tal como se solicitó, pero si la lista de datos es destructible, sugeriría que saque los elementos de la lista hasta que termine con una lista vacía:

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

incluso podría usar while len (element_list) si no necesita hacer nada con el último elemento. Me parece que esta solución es más elegante que la siguiente ().

Retrasar el manejo especial del último elemento hasta después del bucle.

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

Me gusta el enfoque de @ethan-t, pero while True es peligroso desde mi punto de vista.

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

Suponiendo que la entrada es un iterador, aquí hay una manera de usar tee e izip desde 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. 

Manifestación:

 >>> 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 estás revisando la lista, para mí esto también funcionó:

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

La solución más simple que viene a mi mente es:

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

Por lo tanto, siempre miramos hacia adelante un elemento demorando el procesamiento de una iteración. Para omitir hacer algo durante la primera iteración, simplemente detecto el error.

Por supuesto, debe pensar un poco para que se NameError el NameError de nombre cuando lo desee.

También mantenga el `counstruct

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

Esto se basa en que el nombre nuevo no fue definido previamente. Si eres paranoico, puedes asegurarte de que no existan new usando:

 try: del new except NameError: pass 

Alternativamente, también puede, por supuesto, usar una sentencia if ( if notfirst: print(new) else: notfirst = True ). Pero por lo que sé, la sobrecarga es más grande.


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

así que espero que la sobrecarga sea no seleccionable.

Cuente los elementos una vez y manténgase actualizado con la cantidad de elementos 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 

De esta manera solo evalúas la longitud de la lista una vez. Muchas de las soluciones en esta página parecen asumir que la longitud no está disponible por adelantado, pero eso no es parte de su pregunta. Si tienes la longitud, úsala.

Para mí, la forma más simple y pythonica de manejar un caso especial al final de una lista es:

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

Por supuesto, esto también puede usarse para tratar el primer elemento de una manera especial.

Puede haber múltiples maneras. rebanar será más rápido. Añadiendo uno más que usa el 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]