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)
  ...
Alexandros
quelle

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)
Sven Marnach
quelle
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.

kxr
quelle
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.

gddc
quelle
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
Dave Rove
quelle
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)
andrii
quelle
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

John
quelle
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]
keegan3d
quelle
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