Comment choisir un seul élément d'un générateur?

214

J'ai une fonction de générateur comme celle-ci:

def myfunct():
  ...
  yield result

La façon habituelle d'appeler cette fonction serait:

for r in myfunct():
  dostuff(r)

Ma question, existe-t-il un moyen d'obtenir un seul élément du générateur quand je le souhaite? Par exemple, j'aimerais faire quelque chose comme:

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

Réponses:

304

Créer un générateur en utilisant

g = myfunct()

Chaque fois que vous souhaitez un article, utilisez

next(g)

(ou g.next()en Python 2.5 ou inférieur).

Si le générateur sort, il augmentera StopIteration. Vous pouvez soit intercepter cette exception si nécessaire, soit utiliser l' defaultargument pour next():

next(g, default_value)
Sven Marnach
la source
4
Notez que cela n'élèvera StopIteration que lorsque vous essayez d'utiliser g.next () après avoir fourni le dernier élément de g.
Wilduck
26
next(gen, default)peut également être utilisé pour éviter l' StopIterationexception. Par exemple, next(g, None)pour un générateur de chaînes, vous obtiendrez soit une chaîne, soit None une fois l'itération terminée.
Attila
8
en Python 3000, le prochain () est __suivant __ ()
Jonathan Baldwin
27
@JonathanBaldwin: Votre commentaire est quelque peu trompeur. En Python 3, vous devez utiliser la deuxième syntaxe donnée dans ma réponse, next(g). Cela va appeler en interne g.__next__(), mais vous n'avez pas vraiment à vous en soucier, tout comme vous ne vous souciez généralement pas len(a)des appels internes a.__len__().
Sven Marnach
14
J'aurais dû être plus clair. g.next()est g.__next__()en py3k. La fonction intégrée next(iterator)existe depuis Python 2.6, et c'est ce qui devrait être utilisé dans tout le nouveau code Python, et il est trivial de la réimplémenter si vous devez prendre en charge py <= 2.5.
Jonathan Baldwin
29

Pour choisir un seul élément d'un générateur à utiliser breakdans une forinstruction, oulist(itertools.islice(gen, 1))

Selon votre exemple (littéralement), vous pouvez faire quelque chose comme:

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

Si vous voulez " obtenir un seul élément du générateur [une fois généré] chaque fois que je le souhaite " (je suppose que 50% c'est l'intention d'origine et l'intention la plus courante) alors:

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

De cette façon, l'utilisation explicite de generator.next()peut être évitée, et la gestion de fin d'entrée ne nécessite pas de StopIterationgestion d'exceptions (cryptique) ou de comparaisons de valeurs par défaut supplémentaires.

La section d'instructions else:of forn'est nécessaire que si vous voulez faire quelque chose de spécial en cas de fin de générateur.

Remarque sur next()/ .next():

En Python3, la .next()méthode a été renommée pour .__next__()une bonne raison: elle est considérée comme de bas niveau (PEP 3114). Avant Python 2.6, la fonction intégrée next()n'existait pas. Et il a même été discuté de passer next()au operatormodule (ce qui aurait été judicieux), en raison de son besoin rare et de l'inflation douteuse des noms intégrés.

L'utilisation next()sans défaut est toujours une pratique de très bas niveau - jetant StopIterationouvertement le cryptique comme un boulon dans le code d'application normal. Et l'utilisation next()avec la sentinelle par défaut - qui devrait être la meilleure option pour une entrée next()directe builtins- est limitée et donne souvent raison à une logique / lisibilité non pythonique étrange.

Bottom line: L'utilisation de next () devrait être très rare - comme l'utilisation des fonctions du operatormodule. En utilisant for x in iterator, islice, list(iterator)et d' autres fonctions acceptant un itérateur est en toute transparence la façon naturelle d'utiliser itérateurs au niveau de l' application - et tout à fait toujours possible. next()est de bas niveau, un concept supplémentaire, non évident - comme le montre la question de ce fil. Tandis que, par exemple, l'utilisation breakdans forest conventionnelle.

kxr
la source
8
C'est beaucoup trop de travail pour obtenir simplement le premier élément d'un résultat de liste. Souvent, je n'en ai pas besoin pour être paresseux mais je n'ai pas le choix dans py3. N'y a-t-il rien de semblable mySeq.head?
javadba
2

Je ne pense pas qu'il existe un moyen pratique de récupérer une valeur arbitraire à partir d'un générateur. Le générateur fournira une méthode next () pour se déplacer, mais la séquence complète n'est pas produite immédiatement pour économiser de la mémoire. C'est la différence fonctionnelle entre un générateur et une liste.

gddc
la source
1

Pour ceux d'entre vous qui parcourent ces réponses pour un exemple de travail complet pour Python3 ... et bien voilà:

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)

Cela produit:

1001
1002
1003
Dave Rove
la source
1

Le générateur est une fonction qui produit un itérateur. Par conséquent, une fois que vous avez une instance d'itérateur, utilisez next () pour extraire l'élément suivant de l'itérateur. À titre d'exemple, utilisez la fonction next () pour récupérer le premier élément, puis utilisez-la for inpour traiter les éléments restants:

# 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
la source
0
generator = myfunct()
while True:
   my_element = generator.next()

assurez-vous d'attraper l'exception levée après la prise du dernier élément

John
la source
Non valable pour Python 3, voir l'excellente réponse de kxr .
clacke
2
Remplacez simplement "generator.next ()" par "next (generator)" pour Python 3.
iyop45
-3

Je crois que la seule façon est d'obtenir une liste de l'itérateur puis d'obtenir l'élément que vous voulez de cette liste.

l = list(myfunct())
l[4]
keegan3d
la source
La réponse de Sven est probablement meilleure, mais je laisserai cela ici au cas où cela serait plus conforme à vos besoins.
keegan3d
26
Assurez-vous que vous disposez d'un générateur fini avant de le faire.
Seth
6
Désolé, cela a de la complexité sur la longueur de l'itérateur, alors que le problème est évidemment O (1).
yo
1
Trop de mémoire et de processus à aspirer du générateur! De plus, comme @Seth l'a mentionné précédemment, les générateurs ne sont pas garantis pour, quand arrêter de générer.
Pylover
Ce n'est évidemment pas le seul moyen (ou le meilleur moyen s'il myfunct()génère un grand nombre de valeurs) puisque vous pouvez utiliser la fonction intégrée nextpour obtenir la prochaine valeur générée.
HelloGoodbye