Was ist die pythonische Art, das letzte Element in einer Python 'für' Schleife zu erkennen?

Ich würde gerne den besten Weg kennenlernen (kompakter und "pythonisch"), um eine spezielle Behandlung für das letzte Element in einer for-Schleife zu machen. Es gibt ein Stück Code, das nur zwischen den Elementen genannt werden sollte, in der letzten unterdrückt.

Hier ist, wie ich es derzeit mache:

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 

Gibt es einen besseren Weg?

Hinweis: Ich möchte es nicht mit Hacks machen, wie zB reduce 😉

16 Solutions collect form web for “Was ist die pythonische Art, das letzte Element in einer Python 'für' Schleife zu erkennen?”

Die meisten der Zeit ist es einfacher (und billiger), die erste Iteration der Spezialfall statt der letzten zu machen:

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

Dies wird für jeden iterable funktionieren, auch für diejenigen, die keine len() :

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

Abgesehen davon, ich glaube nicht, dass es eine allgemein überlegene Lösung gibt, da es davon abhängt, was Sie versuchen zu tun. Zum Beispiel, wenn Sie einen String aus einer Liste str.join() , ist es natürlich besser, str.join() als mit einer for Schleife "mit Spezialfall" zu verwenden.


Nach dem gleichen Prinzip aber kompakter:

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

Sieht vertraut aus, nicht wahr? 🙂


Für @ofko und andere, die wirklich herausfinden müssen, ob der aktuelle Wert eines iterable ohne len() der letzte ist, müssen Sie nach vorne schauen:

 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 

Dann kannst du es so verwenden:

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

Der "Code zwischen" ist ein Beispiel für das Head-Tail- Muster.

Sie haben ein Element, dem eine Folge von (zwischen, Item) Paaren folgt. Sie können dies auch als eine Folge von (Element, zwischen) Paaren, gefolgt von einem Element. Es ist in der Regel einfacher, das erste Element als Special und alle anderen als "Standard" Fall zu nehmen.

Weiterhin, um einen Code zu vermeiden, müssen Sie eine Funktion oder ein anderes Objekt zur Verfügung stellen, um den Code zu enthalten, den Sie nicht wiederholen möchten. Einbetten einer if- Anweisung in eine Schleife, die immer falsch ist, außer einer Zeit ist irgendwie albern.

 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 ) 

Dies ist zuverlässiger, weil es etwas leichter zu beweisen ist, es erstellt keine zusätzliche Datenstruktur (dh eine Kopie einer Liste) und erfordert nicht viel verschwendete Ausführung einer if- Bedingung, die immer falsch ist, außer einmal.

Wenn du einfach data_list das letzte Element in data_list zu ändern, data_list du einfach die Notation verwenden:

 L[-1] 

Allerdings sieht es so aus, als hättest du mehr als das. Es gibt nichts wirklich falsch mit deinem Weg. Ich habe sogar einen kurzen Blick auf einige Django-Code für ihre Vorlage-Tags und sie tun im Grunde, was du tust.

Dies ähnelt dem Ansatz von Ants Aasma, aber ohne das itertools-Modul zu verwenden. Es ist auch ein nacheilender Iterator, der ein einziges Element im Iteratorstrom sieht:

 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 

Obwohl diese Frage ziemlich alt ist, kam ich hier über Google und ich fand einen ganz einfachen Weg: List slicing. Angenommen, du willst ein '&' zwischen allen Listeneinträgen setzen.

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

Dies gibt '1 & 2 & 3' zurück.

Sie können ein Schiebefenster über die Eingabedaten verwenden, um einen Blick auf den nächsten Wert zu bekommen und einen Sentinel zu verwenden, um den letzten Wert zu erkennen. Das funktioniert auf jedem iterable, also musst du die Länge vorher nicht kennen. Die paarweise Umsetzung ist von itertools Rezepte .

 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 

Gibt es keine Möglichkeit, über alles zu iterieren – aber das letzte Element, und behandle das letzte außerhalb der Schleife? Immerhin wird eine Schleife erstellt, um etwas zu tun, das allen Elementen ähnelt, die Sie umschleifen; Wenn ein Element etwas Besonderes braucht, sollte es nicht in der Schleife sein.

(Siehe auch diese Frage: Ist-die-letzte-Element-in-a-Schleife-verdient-eine-separate-Behandlung )

EDIT: Da die Frage mehr über die "dazwischen" ist, ist entweder das erste Element das besondere, dass es keinen Vorgänger hat, oder das letzte Element ist besonders, dass es keinen Nachfolger hat.

Verwenden Sie das Slicing und prüfen Sie das letzte Element:

 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 : Dies funktioniert nur, wenn alle Elemente in der Liste tatsächlich unterschiedlich sind (haben unterschiedliche Speicherorte im Speicher). Unter der Kapuze kann Python gleiche Elemente erkennen und dieselben Objekte für sie wiederverwenden. Zum Beispiel für Strings mit dem gleichen Wert und gemeinsamen ganzen Zahlen.

Es ist nichts falsch mit deinem Weg, es sei denn, du wirst 100 000 Loops haben und will 100 000 "if" Aussagen sparen. In diesem Fall kannst du so gehen:

 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 

Ausgänge:

 1 2 3 3 

Aber wirklich, in deinem Fall fühle ich mich wie es übertrieben ist.

Auf jeden Fall werden Sie wahrscheinlich mit dem Schneiden glücklich sein:

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

oder nur :

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

Irgendwann ein Kuss Weg, um Sie Sachen, und das würde mit jedem iterable arbeiten, einschließlich der ohne __len__ :

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

Ouputs:

 1 2 3 3 

Wenn ich mich fühle, würde ich es so machen, scheint mir einfach zu sein.

Angenommen, als Iterator, hier ist ein Weg mit tee und izip von 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. 

Demo:

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

Wenn du durch die Liste gehst, für mich das auch gearbeitet:

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

Google hat mich zu dieser alten Frage gebracht und ich glaube, ich könnte einen anderen Ansatz für dieses Problem hinzufügen.

Die meisten Antworten hier würden sich mit einer ordnungsgemäßen Behandlung eines for-Loop-Controllers befassen, wie es gefragt wurde, aber wenn die data_list zerstörbar ist, würde ich vorschlagen, dass du die Items aus der Liste klickst, bis du mit einer leeren Liste aufkommst:

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

Du könntest ja sogar bei len (element_list) benutzen, wenn du nichts mit dem letzten Element machen musst. Ich finde diese Lösung eleganter und dann mit dem nächsten ().

Wenn die Gegenstände eindeutig sind:

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

andere Optionen:

 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 

Die einfachste Lösung, die mir in den Sinn kommt, ist:

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

Also schauen wir immer einen Artikel vor, indem wir die Verarbeitung einer Iteration verzögern. Um etwas während der ersten Iteration zu überspringen, fange ich einfach den Fehler auf.

Natürlich musst du ein bisschen nachdenken, damit der NameError aufgewachsen ist, wenn du es willst.

Halten Sie auch den "

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

Dies beruht darauf, dass der Name neu vorher nicht definiert wurde. Wenn Sie paranoid sind, können Sie sicherstellen, dass new nicht existiert:

 try: del new except NameError: pass 

Alternativ können Sie natürlich auch eine if-Anweisung verwenden ( if notfirst: print(new) else: notfirst = True ). Aber soweit ich weiß, ist der Overhead größer.


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

So erwarte ich, dass der Overhead unaufhebbar ist.

Zählen Sie die Artikel einmal und halten Sie sich mit der Anzahl der verbleibenden Artikel:

 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 

Auf diese Weise werten Sie nur einmal die Länge der Liste aus. Viele der Lösungen auf dieser Seite scheinen zu übernehmen, dass die Länge im Voraus nicht verfügbar ist, aber das ist nicht Teil Ihrer Frage. Wenn du die Länge hast, benutze es.

Verzögert die spezielle Handhabung des letzten Gegenstandes bis nach der Schleife.

 >>> for i in (1, 2, 3): ... pass ... >>> i 3 
Python ist die beste Programmiersprache der Welt.