Puisque Python 3.3, si une fonction de générateur renvoie une valeur, cela devient la valeur de l'exception StopIteration qui est déclenchée. Cela peut être collecté de plusieurs façons:
yield from
, Ce qui implique que la fonction englobante est également un générateur.next()
ou .send()
dans un bloc try/except.Cependant, si je veux simplement parcourir le générateur dans une boucle for - la manière la plus simple - il ne semble pas y avoir de moyen de collecter la valeur de l'exception StopIteration, et donc la valeur de retour. Im en utilisant un exemple simple où le générateur donne des valeurs et renvoie une sorte de résumé à la fin (totaux cumulés, moyennes, statistiques de synchronisation, etc.).
for i in produce_values():
do_something(i)
values_summary = ....??
Une façon consiste à gérer la boucle moi-même:
values_iter = produce_values()
try:
while True:
i = next(values_iter)
do_something(i)
except StopIteration as e:
values_summary = e.value
Mais cela gâche la simplicité de la boucle for. Je ne peux pas utiliser yield from
Car cela nécessite que le code appelant soit lui-même un générateur. Existe-t-il un moyen plus simple que la boucle roll-ones-own pour la boucle illustrée ci-dessus?
En combinant les réponses de @Chad S. et @KT, la plus simple semble transformer ma fonction de générateur en classe en utilisant le protocole itérateur:
class ValueGenerator():
def __iter__(self):
yield 1
yield 2
# and so on
self.summary = {...}
vg = ValueGenerator()
for i in vg:
do_something(i)
values_summary = vg.summary
Et la réponse de @Ferdinand Beyer est la plus simple si je ne peux pas refactoriser le producteur de valeur.
Vous pouvez considérer l'attribut value
de StopIteration
(et sans doute StopIteration
lui-même) comme des détails d'implémentation, non conçus pour être utilisés dans du code "normal".
Jetez un oeil à PEP 38 qui spécifie le yield from
fonctionnalité de Python 3.3: Il explique que certaines alternatives d'utilisation de StopIteration
pour transporter la valeur de retour sont envisagées.
Comme vous n'êtes pas censé obtenir la valeur de retour dans une boucle for
ordinaire, il n'y a pas de syntaxe pour cela. De la même manière que vous n'êtes pas censé attraper explicitement le StopIteration
.
Une bonne solution pour votre situation serait une petite classe d'utilité (pourrait être assez utile pour la bibliothèque standard):
class Generator:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
self.value = yield from self.gen
Cela enveloppe tout générateur et capture sa valeur de retour pour être inspecté plus tard:
>>> def test():
... yield 1
... return 2
...
>>> gen = Generator(test())
>>> for i in gen:
... print(i)
...
1
>>> print(gen.value)
2
Vous pourriez créer un wrapper d'aide, qui intercepterait le StopIteration
et en extrairait la valeur pour vous:
from functools import wraps
class ValueKeepingGenerator(object):
def __init__(self, g):
self.g = g
self.value = None
def __iter__(self):
self.value = yield from self.g
def keep_value(f):
@wraps(f)
def g(*args, **kwargs):
return ValueKeepingGenerator(f(*args, **kwargs))
return g
@keep_value
def f():
yield 1
yield 2
return "Hi"
v = f()
for x in v:
print(x)
print(v.value)
Une façon légère de gérer la valeur de retour (qui n'implique pas l'instanciation d'une classe auxiliaire) consiste à utiliser injection de dépendance .
A savoir, on peut passer la fonction pour gérer/agir sur la valeur de retour en utilisant la fonction de générateur wrapper/helper suivante:
def handle_return(generator, func):
returned = yield from generator
func(returned)
Par exemple, ce qui suit ...
def generate():
yield 1
yield 2
return 3
def show_return(value):
print('returned: {}'.format(value))
for x in handle_return(generate(), show_return):
print(x)
résulte en--
1
2
returned: 3
La méthode la plus évidente à laquelle je peux penser serait un type défini par l'utilisateur qui se souviendrait du résumé pour vous.
>>> import random
>>> class ValueProducer:
... def produce_values(self, n):
... self._total = 0
... for i in range(n):
... r = random.randrange(n*100)
... self._total += r
... yield r
... self.value_summary = self._total/n
... return self.value_summary
...
>>> v = ValueProducer()
>>> for i in v.produce_values(3):
... print(i)
...
25
55
179
>>> print(v.value_summary)
86.33333333333333
>>>