web-dev-qa-db-fra.com

Retour dans le générateur avec rendement en Python 3.3

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é?

45
scdmb

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.

50
user395760

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 StopIterationmust 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))
27
Martijn Pieters

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]

0
Hubert Grzeskowiak