Dans Python 2, il y avait une erreur lorsque return était accompagné de yield dans la définition de la fonction. Mais pour ce code dans Python 3.3
def f():
return 3
yield 2
x = f()
print(x.__next__())
il n'y a aucune erreur que return soit utilisé en fonction avec yield. Cependant, lorsque la fonction __next__
est appelé puis une exception StopIteration est levée. Pourquoi il n'y a pas que la valeur renvoyée 3
? Ce retour est-il en quelque sorte ignoré?
Ceci est une nouvelle fonctionnalité de Python 3.3 (en tant que commentaire, cela ne fonctionne même pas en 3.2). Tout comme return
dans un générateur a longtemps été équivalent à raise StopIteration()
, return <something>
dans un générateur est maintenant équivalent à raise StopIteration(<something>)
. Pour cette raison, l'exception que vous voyez doit être imprimée comme StopIteration: 3
, et la valeur est accessible via l'attribut value
sur l'objet d'exception. Si le générateur est délégué à l'utilisation de la syntaxe (également nouvelle) yield from
, c'est le résultat. Voir PEP 38 pour plus de détails.
def f():
return 1
yield 2
def g():
x = yield from f()
print(x)
# g is still a generator so we need to iterate to run it:
for _ in g():
pass
Ceci imprime 1
, Mais pas 2
.
La valeur de retour n'est pas ignorée, mais uniquement les générateurs yield valeurs, un return
termine simplement le générateur, dans ce cas plus tôt. L'avancement du générateur n'atteint jamais l'instruction yield
dans ce cas.
Chaque fois qu'un itérateur atteint la "fin" des valeurs à produire, un StopIteration
must doit être levé. Les générateurs ne font pas exception. À partir de Python 3.3 cependant, toute expression return
devient la valeur de l'exception:
>>> def gen():
... return 3
... yield 2
...
>>> try:
... next(gen())
... except StopIteration as ex:
... e = ex
...
>>> e
StopIteration(3,)
>>> e.value
3
Utilisez la fonction next()
pour faire avancer les itérateurs, au lieu d'appeler directement .__next__()
:
print(next(x))
Cette réponse n'a aucun rapport avec la question, mais peut être utile aux personnes qui se retrouvent ici après une recherche sur le Web.
Voici une petite fonction d'aide qui transformera toute valeur de retour éventuelle en valeur renvoyée:
def generator():
yield 1
yield 2
return 3
def yield_all(gen):
while True:
try:
yield next(gen)
except StopIteration as e:
yield e.value
break
print([i for i in yield_all(generator())])
[1, 2, 3]
Ou en tant que décorateur:
import functools
def yield_all(func):
gen = func()
@functools.wraps(func)
def wrapper():
while True:
try:
yield next(gen)
except StopIteration as e:
yield e.value
break
return wrapper
@yield_all
def a():
yield 1
yield 2
return 3
print([i for i in a()])
[1, 2, 3]