Retorno en generador junto con rendimiento

3 minutos de lectura

avatar de usuario
scdmb

En Python 2 hubo un error cuando return estaba junto con yield en una definición de función. Pero para este código en Python 3.3:

def f():
  return 3
  yield 2
  
x = f()
print(x.__next__())

no hay error que return se utiliza en función de yield. Sin embargo, cuando la función __next__ se llama entonces se lanza una excepción StopIteration. Por qué no solo hay valor devuelto 3? Es esto return de alguna manera ignorado?

Esta es una característica nueva en Python 3.3 (como señala un comentario, ni siquiera funciona en 3.2). Muy parecido return en un generador ha sido durante mucho tiempo equivalente a raise StopIteration(), return <something> en un generador es ahora equivalente a raise StopIteration(<something>). Por esa razón, la excepción que está viendo debe imprimirse como StopIteration: 3y el valor es accesible a través del atributo value en el objeto de excepción. Si el generador está delegado para usar el (también nuevo) yield from sintaxis, es el resultado. Ver PEP 380 para detalles.

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

esto imprime 1pero no 2.

avatar de usuario
Martijn Pieters

El valor de retorno no se ignora, pero solo los generadores rendir valores, un return simplemente finaliza el generador, en este caso antes de tiempo. El avance del generador nunca llega al yield declaración en ese caso.

Cada vez que un iterador alcanza el ‘final’ de los valores a producir, un StopIteration deber ser criado. Los generadores no son una excepción. Sin embargo, a partir de Python 3.3, cualquier return expresión se convierte en el valor de la excepción:

>>> def gen():
...     return 3
...     yield 2
... 
>>> try:
...     next(gen())
... except StopIteration as ex:
...     e = ex
... 
>>> e
StopIteration(3,)
>>> e.value
3

Utilizar el next() función para avanzar iteradores, en lugar de llamar .__next__() directamente:

print(next(x))

  • return con un valor no se ignora, es un error de sintaxis (en 3.2 y versiones anteriores) o no se ignora (en 3.3 y versiones posteriores).

    usuario395760

    27 mayo 2013 a las 20:20


  • @delnan: el valor no se usa; no se puede utilizar return en lugar de un yield. La función termina, así que StopIteration se eleva y el valor de retorno es, en el mejor de los casos, descartado. el OP sabe antes de 3.3, una devolución con valor es un error de sintaxis.

    – Martijn Pieters

    27 mayo 2013 a las 20:21


  • return de hecho no es un reemplazo para yieldpero el valor es no en vano. Está disponible como un atributo del objeto de excepción, y yield from da un cómodo acceso a la misma.

    usuario395760

    27 mayo 2013 a las 20:23

  • @delnan: Correcto, lo acabo de encontrar: el atributo de valor de la instancia de StopIteration planteada […] se puede configurar explícitamente […] o automáticamente devolviendo un valor del subgenerador;

    – Martijn Pieters

    27 mayo 2013 a las 20:27

¿Ha sido útil esta solución?