Wie wähle ich nur ein Element aus einem Generator aus (in Python)?


213

Ich habe eine Generatorfunktion wie die folgende:

def myfunct():
  ...
  yield result

Der übliche Weg, diese Funktion aufzurufen, wäre:

for r in myfunct():
  dostuff(r)

Meine Frage, gibt es eine Möglichkeit, immer nur ein Element aus dem Generator zu holen, wann immer ich möchte? Zum Beispiel möchte ich etwas tun wie:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...

Antworten:


304

Erstellen Sie einen Generator mit

g = myfunct()

Verwenden Sie jedes Mal, wenn Sie einen Artikel möchten

next(g)

(oder g.next()in Python 2.5 oder niedriger).

Wenn der Generator ausgeht, wird er angehoben StopIteration. Sie können diese Ausnahme bei Bedarf entweder abfangen oder das folgende defaultArgument verwenden next():

next(g, default_value)

4
Beachten Sie, dass StopIteration nur ausgelöst wird, wenn Sie versuchen, g.next () zu verwenden, nachdem das letzte Element in g bereitgestellt wurde.
Wilduck

26
next(gen, default)kann auch verwendet werden, um die StopIterationAusnahme zu vermeiden . Zum Beispiel next(g, None)ergibt ein Generator von Strings nach Abschluss der Iteration entweder einen String oder None.
Attila

8
in Python 3000 ist next () __next __ ()
Jonathan Baldwin

27
@ JonathanBaldwin: Dein Kommentar ist etwas irreführend. In Python 3 würden Sie die zweite in meiner Antwort angegebene Syntax verwenden next(g). Dies wird intern nennen g.__next__(), aber Sie wirklich nicht zu befürchten , dass haben, so wie man in der Regel nicht , dass es egal len(a)intern telefonieren a.__len__().
Sven Marnach

14
Ich hätte klarer sein sollen. g.next()ist g.__next__()in py3k. Das eingebaute System next(iterator)gibt es seit Python 2.6 und sollte in allen neuen Python-Codes verwendet werden. Eine Backimplementierung ist trivial, wenn Sie py <= 2.5 unterstützen müssen.
Jonathan Baldwin

29

Verwenden Sie zum Auswählen nur eines Elements eines Generators breakin einer forAnweisung oderlist(itertools.islice(gen, 1))

Nach Ihrem Beispiel (im wahrsten Sinne des Wortes) können Sie Folgendes tun:

while True:
  ...
  if something:
      for my_element in myfunct():
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Wenn Sie " nur ein Element vom [einmal generierten] Generator erhalten möchten , wann immer ich möchte " (ich nehme an, 50% sind die ursprüngliche Absicht und die häufigste Absicht), dann:

gen = myfunct()
while True:
  ...
  if something:
      for my_element in gen:
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Auf diese Weise kann die explizite Verwendung von generator.next()vermieden werden, und die Behandlung am Ende der Eingabe erfordert keine (kryptische) StopIterationAusnahmebehandlung oder zusätzliche Standardwertvergleiche.

Der Abschnitt mit else:der forAnweisung wird nur benötigt, wenn Sie im Falle eines Generatorendes etwas Besonderes tun möchten.

Hinweis zu next()/ .next():

In Python3 wurde die .next()Methode .__next__()aus gutem Grund umbenannt: Sie gilt als Low-Level (PEP 3114). Vor Python 2.6 existierte die eingebaute Funktion next()nicht. Und es wurde sogar diskutiert, next()auf das operatorModul umzusteigen (was klug gewesen wäre), da es nur selten benötigt wird und die eingebauten Namen fragwürdig aufgeblasen sind.

Die Verwendung next()ohne Standard ist immer noch eine sehr einfache Übung - das Werfen der Kryptik StopIterationwie ein Blitz aus heiterem Himmel im normalen Anwendungscode offen. Und die Verwendung next()mit Standard-Sentinel - was am besten die einzige Option für ein next()direktes In sein sollte builtins- ist begrenzt und führt häufig zu ungerader nicht-pythonischer Logik / Lesbarkeit.

Fazit: Die Verwendung von next () sollte sehr selten sein - wie die Verwendung von Funktionen des operatorModuls. Mit for x in iterator, islice, list(iterator)und anderen Funktionen einen Iterator nahtlos zu akzeptieren ist die natürliche Art und Weise von Iteratoren auf Anwendungsebene mit - und ganz immer möglich. next()ist Low-Level, ein zusätzliches Konzept, nicht offensichtlich - wie die Frage dieses Threads zeigt. Während zB die Verwendung breakin forist konventionell.


8
Dies ist viel zu viel Arbeit, um nur das erste Element eines Listenergebnisses zu erhalten. Oft genug brauche ich es nicht, um faul zu sein, aber ich habe keine Wahl in py3. Gibt es nicht etwas Ähnliches mySeq.head?
Javadba

2

Ich glaube nicht, dass es eine bequeme Möglichkeit gibt, einen beliebigen Wert von einem Generator abzurufen. Der Generator bietet eine next () -Methode, um sich selbst zu durchlaufen, aber die vollständige Sequenz wird nicht sofort erzeugt, um Speicherplatz zu sparen. Das ist der funktionale Unterschied zwischen einem Generator und einer Liste.


1

Für diejenigen unter Ihnen, die diese Antworten nach einem vollständigen Arbeitsbeispiel für Python3 durchsuchen ... Nun, los geht's:

def numgen():
    x = 1000
    while True:
        x += 1
        yield x

nums = numgen() # because it must be the _same_ generator

for n in range(3):
    numnext = next(nums)
    print(numnext)

Dies gibt aus:

1001
1002
1003

1

Generator ist eine Funktion, die einen Iterator erzeugt. Verwenden Sie daher, sobald Sie eine Iteratorinstanz haben, next () , um das nächste Element aus dem Iterator abzurufen. Verwenden Sie beispielsweise die Funktion next (), um das erste Element abzurufen, und verwenden Sie es später for in, um verbleibende Elemente zu verarbeiten:

# create new instance of iterator by calling a generator function
items = generator_function()

# fetch and print first item
first = next(items)
print('first item:', first)

# process remaining items:
for item in items:
    print('next item:', item)

0
generator = myfunct()
while True:
   my_element = generator.next()

Stellen Sie sicher, dass Sie die Ausnahme abfangen, die ausgelöst wird, nachdem das letzte Element übernommen wurde


Nicht gültig für Python 3, siehe die ausgezeichnete Antwort von kxr .
Clacke

2
Ersetzen Sie einfach "generator.next ()" durch "next (generator)" für Python 3.
iyop45

-3

Ich glaube, der einzige Weg ist, eine Liste vom Iterator zu erhalten und dann das gewünschte Element aus dieser Liste zu erhalten.

l = list(myfunct())
l[4]

Die Antwort von Sven ist wahrscheinlich besser, aber ich lasse dies hier, falls es mehr Ihren Bedürfnissen entspricht.
Keegan3d

26
Stellen Sie sicher, dass Sie einen endlichen Generator haben, bevor Sie dies tun.
Seth

6
Entschuldigung, dies hat Komplexität in der Länge des Iterators, während das Problem offensichtlich O (1) ist.
yo

1
Verschwenden Sie zu viel Speicher und Prozess, um vom Generator zu saugen! Wie bereits bei @Seth erwähnt, kann für Generatoren nicht garantiert werden, wann die Generierung beendet werden soll.
Pylover

Das ist offensichtlich nicht der einzige Weg (oder der beste Weg, wenn myfunct()eine große Anzahl von Werten generiert wird), da Sie die integrierte Funktion verwenden können next, um den nächsten generierten Wert abzurufen.
HelloGoodbye
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.