Was ist die pythonische Methode, um das letzte Element in einer 'for'-Schleife zu erkennen?

186

Ich möchte wissen, wie man am besten (kompakter und "pythonischer") das letzte Element in einer for-Schleife speziell behandelt. Es gibt einen Code, der nur zwischen Elementen aufgerufen werden sollte und im letzten unterdrückt wird.

So mache ich das derzeit:

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 wie using machen reduce.;)

e.tadeu
quelle
Was ist mit dem ersten? Sollte es auch unterdrückt werden?
Adam Matan
Können Sie uns sagen, was zwischen den Elementen gemacht wird?
SilentGhost
2
Ich möchte die Antwort für einen generischen Fall erhalten, aber ein konkreter Fall, in dem ich dies benötige, besteht darin, Dinge in einen Stream zu schreiben, zwischen denen sich Trennzeichen befinden, genau wie stream.write (',' .join (name_list)). aber es in einer for-Schleife zu tun, ohne die Zeichenfolgen zu verketten, weil es viele Schreibvorgänge gibt ...
e.tadeu
Siehe auch
codeape
Die ersten drei Zeilen dieser Antwort haben mir wirklich geholfen, hatten eine ähnliche Herausforderung.
Kardamom

Antworten:

151

In den meisten Fällen ist es einfacher (und billiger), die erste Iteration anstelle der letzten zum Sonderfall zu machen :

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

    item()

Dies funktioniert für alle iterierbaren Elemente, auch für diejenigen, die keine haben 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 glaube ich nicht, dass es eine allgemein überlegene Lösung gibt, da dies davon abhängt, was Sie versuchen zu tun. Wenn Sie beispielsweise eine Zeichenfolge aus einer Liste erstellen, ist es natürlich besser, diese zu verwenden, str.join()als eine forSchleife „mit Sonderfall“ zu verwenden.


Nach dem gleichen Prinzip, aber kompakter:

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

Kommt mir bekannt vor, nicht wahr? :) :)


Für @ofko und andere, die wirklich herausfinden müssen, ob der aktuelle Wert eines iterierbaren 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 können Sie es so verwenden:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False
Ferdinand Beyer
quelle
1
Es stimmt, dieser Weg scheint besser zu sein als meiner, zumindest muss er nicht enumerate und len verwenden.
e.tadeu
Ja, aber es wird eine weitere ifhinzugefügt, die vermieden werden könnte, wenn die Schleife in zwei Schleifen aufgeteilt würde. Dies ist jedoch nur relevant, wenn eine große Datenliste iteriert wird.
Adam Matan
Das Problem beim Aufteilen in zwei Schleifen besteht darin, dass entweder DRY verletzt wird oder Sie gezwungen werden, Methoden zu definieren.
e.tadeu
Ich versuche wirklich, Ihr letztes Beispiel zu verstehen (das in meinem Code einwandfrei funktioniert), aber ich verstehe nicht, wie es funktioniert (die Idee dahinter)
Olivier Pons
1
@OlivierPons Sie müssen das Iteratorprotokoll von Python verstehen: Ich erhalte einen Iterator für ein Objekt und rufe den ersten Wert mit ab next(). Dann nutze ich, dass ein Iterator für sich iterierbar ist, damit ich ihn in der forSchleife bis zur Erschöpfung verwenden kann, Iteration vom zweiten bis zum letzten Wert. Währenddessen behalte ich den aktuellen Wert, den ich lokal vom Iterator abgerufen habe, und yieldstattdessen den letzten. Auf diese Weise weiß ich, dass noch ein weiterer Wert zu erwarten ist. Nach der for-Schleife habe ich jeden Wert außer dem letzten gemeldet.
Ferdinand Beyer
20

Obwohl diese Frage ziemlich alt ist, bin ich über Google hierher gekommen und habe einen ganz einfachen Weg gefunden: List Slicing. Angenommen, Sie möchten ein '&' zwischen alle Listeneinträge 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.

BeckmaR
quelle
7
Sie haben gerade die Join-Funktion neu implementiert: "&" .join ([str (x) für x in l])
Bryan Oakley
Die Verkettung von Zeichenfolgen ist etwas ineffizient. Wenn len(l)=1000000in diesem Beispiel Programm wird für eine Weile laufen. appendwird afaik empfohlen. l=[1,2,3]; l.append(4);
Plhn
18

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

Sie haben einen Artikel, auf den eine Folge von (zwischen Artikel-) Paaren folgt. Sie können dies auch als eine Folge von (Element, zwischen) Paaren gefolgt von einem Element anzeigen. Es ist im Allgemeinen einfacher, das erste Element als etwas Besonderes und alle anderen als "Standard" -Fall zu betrachten.

Um zu vermeiden, dass sich Code wiederholt, müssen Sie eine Funktion oder ein anderes Objekt bereitstellen, um den Code zu enthalten, den Sie nicht wiederholen möchten. Das Einbetten einer if- Anweisung in eine Schleife, die bis auf ein einziges Mal immer falsch ist, 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, da es etwas einfacher 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 nur einmal falsch ist.

S.Lott
quelle
4
Funktionsaufrufe sind viel langsamer als ifAnweisungen, sodass das Argument "Verschwendete Ausführung" nicht gilt.
Ferdinand Beyer
1
Ich bin mir nicht sicher, was der Geschwindigkeitsunterschied zwischen Funktionsaufruf und if-Anweisung mit irgendetwas zu tun hat. Der Punkt ist, dass diese Formulierung keine if-Anweisung hat, die immer falsch ist (außer einmal)
S.Lott
1
Ich habe Ihre Aussage "... und erfordert nicht viel verschwendete Ausführung einer if-Bedingung, die nur einmal falsch ist" als "... und ist schneller, da sie ein paar ifs spart" interpretiert . Offensichtlich beziehen Sie sich nur auf "Code-Sauberkeit"?
Ferdinand Beyer
Wird das Definieren einer Funktion anstelle der Verwendung einer ifAnweisung von der Python-Community als sauberer angesehen?
Markus von Broady
17

Wenn Sie nur das letzte Element in ändern data_listmöchten, können Sie einfach die Notation verwenden:

L[-1]

Es sieht jedoch so aus, als würden Sie mehr als das tun. Es ist nichts wirklich falsch an deinem Weg. Ich habe sogar einen kurzen Blick auf Django-Code für ihre Vorlagen-Tags geworfen und sie tun im Grunde das, was Sie tun.

Bartek
quelle
1
Ich
ändere
4
@ e.tadeu es spielt keine Rolle, ob Sie es ändern oder nicht. Wenn Sie Ihre if-Anweisung in: if data != datalist[-1]:ändern und alles andere gleich lassen, ist dies meiner Meinung nach der beste Weg, dies zu codieren.
Raumfahrer
7
@spacetyper Dies wird unterbrochen, wenn der letzte Wert nicht eindeutig ist.
Ark-Kun
14

wenn die Artikel einzigartig 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 - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code
Palza
quelle
10

Dies ähnelt dem Ansatz von Ants Aasma, jedoch ohne Verwendung des itertools-Moduls. Es ist auch ein verzögerter Iterator, der ein einzelnes Element im Iterator-Stream vorwegnimmt:

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
Andrew Dalke
quelle
4

Sie können ein Schiebefenster über den Eingabedaten verwenden, um einen Blick auf den nächsten Wert zu werfen, und einen Sentinel verwenden, um den letzten Wert zu ermitteln. Dies funktioniert auf jedem iterablen Gerät, sodass Sie die Länge nicht im Voraus kennen müssen. Die paarweise Implementierung basiert auf itertools-Rezepten .

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
Ameisen Aasma
quelle
3

Gibt es keine Möglichkeit, alle außer dem letzten Element zu durchlaufen und das letzte außerhalb der Schleife zu behandeln? Schließlich wird eine Schleife erstellt, um etwas Ähnliches wie alle Elemente zu tun, über die Sie eine Schleife ausführen. Wenn ein Element etwas Besonderes benötigt, sollte es nicht in der Schleife sein.

(Siehe auch diese Frage: Verdient-das-letzte-Element-in-einer-Schleife-eine-separate-Behandlung )

BEARBEITEN: Da es bei der Frage eher um das "Dazwischen" geht, ist entweder das erste Element das besondere, da es keinen Vorgänger hat, oder das letzte Element ist insofern etwas Besonderes, als es keinen Nachfolger hat.

xtofl
quelle
Das letzte Element sollte jedoch ähnlich wie jedes andere Element in der Liste behandelt werden. Das Problem ist die Sache, die nur zwischen Elementen gemacht werden sollte.
e.tadeu
In diesem Fall ist der erste der einzige ohne Vorgänger. Nehmen Sie diesen auseinander und durchlaufen Sie den Rest des allgemeinen Codes der Liste.
xtofl
3

Ich mag den Ansatz von @ ethan-t, ist aber while Trueaus meiner Sicht gefährlich.

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

Hier data_listist so, dass das letzte Element wertmäßig dem ersten der Liste entspricht. L kann mit ausgetauscht werden, data_listaber in diesem Fall ist es nach der Schleife leer. while Truekann auch verwendet werden, wenn Sie überprüfen, ob die Liste vor der Verarbeitung nicht leer ist oder die Prüfung nicht benötigt wird (autsch!).

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

Das Gute daran ist, dass es schnell ist. Das Schlechte - es ist zerstörbar ( data_listwird leer).

Intuitivste Lösung:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

Oh ja, du hast es schon vorgeschlagen!

Aliaksandr Klimovich
quelle
2

Es ist nichts Falsches an Ihrem Weg, es sei denn, Sie haben 100 000 Schleifen und möchten 100 000 "if" -Anweisungen speichern. In diesem Fall können Sie diesen Weg 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 übertrieben.

In jedem Fall werden Sie wahrscheinlich mehr Glück beim Schneiden haben:

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 eine KISS-Methode, um Ihre Sachen zu erledigen, und das würde mit jedem iterablen funktionieren, auch mit denen ohne __len__:

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

Ausgaben:

1
2
3
3

Wenn ich das Gefühl habe, ich würde es so machen, scheint mir das einfach zu sein.

e-satis
quelle
2
Beachten Sie jedoch, dass iterable [-1] nicht für alle iterables (z. B. Generatoren ohne len )
funktioniert
Wenn Sie nur auf das letzte Element nach der Schleife zugreifen möchten, verwenden Sie es einfach, itemanstatt es mit neu zu berechnen list[-1]. Aber trotzdem: Ich glaube nicht, dass das OP darum gebeten hat, oder?
Ferdinand Beyer
Betreff: iterable.__iter__() - Bitte __Funktionen nicht direkt aufrufen . Sollte sein iter(iterable).
PaulMcG
2

Verwenden Sie Slicing und is, um nach dem letzten Element zu suchen:

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>

Vorsichtsmaßnahme : Dies funktioniert nur, wenn alle Elemente in der Liste tatsächlich unterschiedlich sind (unterschiedliche Speicherorte im Speicher haben). Unter der Haube kann Python gleiche Elemente erkennen und dieselben Objekte für sie wiederverwenden. Zum Beispiel für Zeichenfolgen mit demselben Wert und gemeinsamen Ganzzahlen.

Roger Dahl
quelle
2

Wenn Sie die Liste durchgehen, hat dies auch für mich funktioniert:

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

Google hat mich zu dieser alten Frage gebracht, und ich denke, ich könnte diesem Problem einen anderen Ansatz hinzufügen.

Die meisten Antworten hier würden sich mit einer ordnungsgemäßen Behandlung einer for-Schleifensteuerung befassen, wie sie gefragt wurde. Wenn die data_list jedoch zerstörbar ist, würde ich vorschlagen, dass Sie die Elemente aus der Liste entfernen, bis Sie eine leere Liste erhalten:

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

Sie können sogar while len (element_list) verwenden, wenn Sie mit dem letzten Element nichts tun müssen. Ich finde diese Lösung eleganter als den Umgang mit next ().

Anderson Santos
quelle
2

Für mich ist die einfachste und pythonischste Art, einen Sonderfall am Ende einer Liste zu behandeln, folgende:

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

Dies kann natürlich auch verwendet werden, um das erste Element auf besondere Weise zu behandeln.

Chris
quelle
2

Anstatt hochzuzählen, können Sie auch herunterzählen:

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

Verzögern Sie die spezielle Behandlung des letzten Elements bis nach der Schleife.

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

Es gibt mehrere Möglichkeiten. Das Schneiden wird am schnellsten sein. Hinzufügen einer weiteren Methode mit der Methode .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Amolpachpute
quelle
0

Unter der Annahme, dass die Eingabe als Iterator erfolgt, können Sie Tee und izip von itertools folgendermaßen verwenden:

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
>>>
Anon
quelle
0

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

Wir schauen also immer nach vorne, indem wir die Verarbeitung um eine Iteration verzögern. Um etwas während der ersten Iteration zu überspringen, fange ich einfach den Fehler ab.

Natürlich müssen Sie ein bisschen nachdenken, damit das NameErrorerhöht wird, wenn Sie es wollen.

Behalten Sie auch den Rat

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

Dies setzt voraus, dass der Name new zuvor nicht definiert wurde. Wenn Sie paranoid sind, können Sie sicherstellen, dass newdies nicht existiert, indem Sie:

try:
    del new
except NameError:
    pass

Alternativ können Sie natürlich auch eine if-Anweisung ( if notfirst: print(new) else: notfirst = True) verwenden. 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

Daher erwarte ich, dass der Overhead nicht auswählbar ist.

DerWeh
quelle
0

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

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 bewerten Sie die Länge der Liste nur einmal. Viele der Lösungen auf dieser Seite scheinen davon auszugehen, dass die Länge im Voraus nicht verfügbar ist, aber das ist nicht Teil Ihrer Frage. Wenn Sie die Länge haben, verwenden Sie es.

Ethan T.
quelle
0

Eine einfache Lösung, die mir in den Sinn kommt, wäre:

for i in MyList:
    # Check if 'i' is the last element in the list
    if i == MyList[-1]:
        # Do something different for the last
    else:
        # Do something for all other elements

Eine zweite ebenso einfache Lösung könnte durch Verwendung eines Zählers erreicht werden:

# Count the no. of elements in the list
ListLength = len(MyList)
# Initialize a counter
count = 0

for i in MyList:
    # increment counter
    count += 1
    # Check if 'i' is the last element in the list
    # by using the counter
    if count == ListLength:
        # Do something different for the last
    else:
        # Do something for all other elements
Alles ist gut
quelle