Come scegliere un solo oggetto da un generatore?


214

Ho una funzione del generatore come la seguente:

def myfunct():
  ...
  yield result

Il solito modo di chiamare questa funzione sarebbe:

for r in myfunct():
  dostuff(r)

La mia domanda, c'è un modo per ottenere solo un elemento dal generatore ogni volta che mi piace? Ad esempio, vorrei fare qualcosa del tipo:

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

Risposte:


304

Crea un generatore usando

g = myfunct()

Ogni volta che desideri un oggetto, usa

next(g)

(o g.next()in Python 2.5 o precedenti).

Se il generatore esce, si alza StopIteration. Puoi prendere questa eccezione, se necessario, o usare l' defaultargomento per next():

next(g, default_value)

4
Nota, aumenterà StopIteration solo quando si tenta di utilizzare g.next () dopo che è stato fornito l'ultimo elemento in g.
Wilduck,

26
next(gen, default)può anche essere usato per evitare l' StopIterationeccezione. Ad esempio, next(g, None)per un generatore di stringhe verrà prodotta una stringa o None al termine dell'iterazione.
Attila,

8
in Python 3000, next () è __next __ ()
Jonathan Baldwin

27
@JonathanBaldwin: il tuo commento è alquanto fuorviante. In Python 3, si può usare la seconda sintassi indicata nella mia risposta, next(g). Questo chiamerà internamente g.__next__(), ma non devi preoccuparti di ciò, proprio come di solito non ti importa che len(a)chiamate internamente a.__len__().
Sven Marnach,

14
Avrei dovuto essere più chiaro. g.next()è g.__next__()in py3k. Il builtin next(iterator)esiste da Python 2.6, ed è quello che dovrebbe essere usato in tutto il nuovo codice Python, ed è banale eseguire il backimplement se è necessario supportare py <= 2.5.
Jonathan Baldwin,

29

Per selezionare solo un elemento di un generatore utilizzare breakin forun'istruzione, oppurelist(itertools.islice(gen, 1))

Secondo il tuo esempio (letteralmente) puoi fare qualcosa del tipo:

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

Se vuoi " ottenere un solo elemento dal generatore [una volta generato] ogni volta che mi piace " (suppongo che il 50% sia l'intenzione originale e l'intenzione più comune) quindi:

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

In questo modo è generator.next()possibile evitare l' uso esplicito di e la gestione di fine input non richiede la gestione (criptica) delle StopIterationeccezioni o confronti di valori di default aggiuntivi.

La sezione else:of forstatement è necessaria solo se vuoi fare qualcosa di speciale in caso di end-of-generator.

Nota su next()/ .next():

In Python3 il .next()metodo è stato rinominato .__next__()per una buona ragione: è considerato di basso livello (PEP 3114). Prima di Python 2.6 la funzione incorporata next()non esisteva. Ed è stato anche discusso di passare next()al operatormodulo (che sarebbe stato saggio), a causa della sua rara necessità e discutibile inflazione dei nomi predefiniti.

L'uso next()senza impostazione predefinita è ancora una pratica di livello molto basso: lanciare il criptico StopIterationcome un fulmine a ciel sereno nel normale codice dell'applicazione. E l'utilizzo next()con sentinel predefinito - che dovrebbe essere l'unica opzione per un accesso next()diretto builtins- è limitato e spesso dà ragione a strane logiche / leggibilità non pitoniche.

In conclusione: usare next () dovrebbe essere molto raro, come usare le funzioni del operatormodulo. Usando for x in iterator, islice, list(iterator)e altre funzioni di accettare un iteratore senza soluzione di continuità è il modo naturale di usare iteratori sul livello di applicazione - e quasi sempre possibile. next()è di basso livello, un concetto in più, invisibile - come mostra la domanda di questo thread. Mentre ad esempio l'uso di breakin forè convenzionale.


8
Questo è troppo lavoro per ottenere solo il primo elemento del risultato di un elenco. Abbastanza spesso non ho bisogno che sia pigro ma non ho scelta in py3. Non c'è qualcosa di simile mySeq.head?
Javavba,

2

Non credo che ci sia un modo conveniente per recuperare un valore arbitrario da un generatore. Il generatore fornirà un metodo next () per attraversare se stesso, ma l'intera sequenza non viene prodotta immediatamente per risparmiare memoria. Questa è la differenza funzionale tra un generatore e un elenco.


1

Per quelli di voi che scansionano queste risposte per un esempio di lavoro completo per Python3 ... beh ecco qui:

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)

Questo produce:

1001
1002
1003

1

Il generatore è una funzione che produce un iteratore. Pertanto, una volta che si dispone dell'istanza iteratore, utilizzare next () per recuperare l'elemento successivo dall'iteratore. Ad esempio, utilizzare la funzione next () per recuperare il primo elemento e successivamente utilizzare for inper elaborare gli elementi rimanenti:

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

assicurati di rilevare l'eccezione generata dopo che è stato preso l'ultimo elemento


Non valido per Python 3, vedi l'eccellente risposta di kxr .
clacke,

2
Sostituisci semplicemente "generator.next ()" con "next (generator)" per Python 3.
iyop45

-3

Credo che l'unico modo sia ottenere un elenco dall'iteratore e quindi ottenere l'elemento desiderato da tale elenco.

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

La risposta di Sven è probabilmente migliore, ma la lascerò qui nel caso sia più in linea con le tue esigenze.
keegan3d

26
Assicurati di avere un generatore finito prima di farlo.
Seth,

6
Siamo spiacenti, questo ha complessità la lunghezza dell'iteratore, mentre il problema è ovviamente O (1).
giovedì

1
Sprecare troppa memoria e processo per aspirare dal generatore! Inoltre, come accennato in precedenza @Seth, i generatori non sono garantiti per quando interrompere la generazione.
pylover,

Questo ovviamente non è l'unico modo (o il modo migliore se myfunct()genera un gran numero di valori) poiché è possibile utilizzare la funzione integrata nextper ottenere il valore generato successivo.
Ciao Arrivederci
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.