in

En la práctica, ¿cuáles son los usos principales de la nueva sintaxis «rendimiento de» en Python 3.3?

apple touch icon@2

Primero saquemos una cosa. La explicación de que yield from g es equivalente a for v in g: yield v ni siquiera empieza a hacer justicia a qué yield from se trata de. Porque, seamos sinceros, si todos yield from lo que hace es expandir el for bucle, entonces no justifica agregar yield from al lenguaje y evitar que se implementen un montón de nuevas características en Python 2.x.

Qué yield from lo hace es establece una conexión bidireccional transparente entre la persona que llama y el subgenerador:

  • La conexión es «transparente» en el sentido de que también propagará todo correctamente, no solo los elementos que se generan (por ejemplo, se propagan las excepciones).

  • La conexión es «bidireccional» en el sentido de que los datos se pueden enviar tanto de y para un generador.

(Si estuviéramos hablando de TCP, yield from g podría significar «ahora desconecte temporalmente el socket de mi cliente y vuelva a conectarlo a este otro socket del servidor».)

Por cierto, si no está seguro de qué enviando datos a un generador incluso significa que debes dejar todo y leer sobre corrutinas primero, son muy útiles (compárelos con subrutinas), pero desafortunadamente menos conocido en Python. El curioso curso de Dave Beazley sobre corrutinas es un excelente comienzo. Lea las diapositivas 24 a 33 para una imprimación rápida.

Leer datos de un generador usando el rendimiento de

def reader():
    """A generator that fakes a read from a file, socket, etc."""
    for i in range(4):
        yield '<< %s' % i

def reader_wrapper(g):
    # Manually iterate over data produced by reader
    for v in g:
        yield v

wrap = reader_wrapper(reader())
for i in wrap:
    print(i)

# Result
<< 0
<< 1
<< 2
<< 3

En lugar de iterar manualmente reader(), podemos simplemente yield from eso.

def reader_wrapper(g):
    yield from g

Eso funciona y eliminamos una línea de código. Y probablemente la intención sea un poco más clara (o no). Pero nada cambia la vida.

Envío de datos a un generador (corrutina) usando el rendimiento de – Parte 1

Ahora hagamos algo más interesante. Creemos una corrutina llamada writer que acepta los datos que se le envían y escribe en un socket, fd, etc.

def writer():
    """A coroutine that writes data *sent* to it to fd, socket, etc."""
    while True:
        w = (yield)
        print('>> ', w)

Ahora la pregunta es, ¿cómo debe la función de contenedor manejar el envío de datos al escritor, de modo que cualquier dato que se envíe al contenedor sea transparentemente enviado al writer()?

def writer_wrapper(coro):
    # TBD
    pass

w = writer()
wrap = writer_wrapper(w)
wrap.send(None)  # "prime" the coroutine
for i in range(4):
    wrap.send(i)

# Expected result
>>  0
>>  1
>>  2
>>  3

La envoltura necesita aceptar los datos que se le envían (obviamente) y también debe manejar el StopIteration cuando se agota el bucle for. Evidentemente solo haciendo for x in coro: yield x no servirá. Aquí hay una versión que funciona.

def writer_wrapper(coro):
    coro.send(None)  # prime the coro
    while True:
        try:
            x = (yield)  # Capture the value that's sent
            coro.send(x)  # and pass it to the writer
        except StopIteration:
            pass

O podríamos hacer esto.

def writer_wrapper(coro):
    yield from coro

Eso ahorra 6 líneas de código, lo hace mucho más legible y simplemente funciona. ¡Magia!

Envío de datos a un generador de rendimiento de – Parte 2 – Manejo de excepciones

Hagámoslo más complicado. ¿Qué pasa si nuestro escritor necesita manejar excepciones? Digamos el writer maneja un SpamException e imprime *** si encuentra uno.

class SpamException(Exception):
    pass

def writer():
    while True:
        try:
            w = (yield)
        except SpamException:
            print('***')
        else:
            print('>> ', w)

¿Y si no cambiamos? writer_wrapper? ¿Funciona? Intentemos

# writer_wrapper same as above

w = writer()
wrap = writer_wrapper(w)
wrap.send(None)  # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
    if i == 'spam':
        wrap.throw(SpamException)
    else:
        wrap.send(i)

# Expected Result
>>  0
>>  1
>>  2
***
>>  4

# Actual Result
>>  0
>>  1
>>  2
Traceback (most recent call last):
  ... redacted ...
  File ... in writer_wrapper
    x = (yield)
__main__.SpamException

Um, no está funcionando porque x = (yield) solo genera la excepción y todo se detiene por completo. Hagamos que funcione, pero manejando manualmente las excepciones y enviándolas o lanzándolas al subgenerador (writer)

def writer_wrapper(coro):
    """Works. Manually catches exceptions and throws them"""
    coro.send(None)  # prime the coro
    while True:
        try:
            try:
                x = (yield)
            except Exception as e:   # This catches the SpamException
                coro.throw(e)
            else:
                coro.send(x)
        except StopIteration:
            pass

Esto funciona.

# Result
>>  0
>>  1
>>  2
***
>>  4

¡Pero esto también!

def writer_wrapper(coro):
    yield from coro

los yield from maneja de forma transparente el envío de valores o el lanzamiento de valores al subgenerador.

Sin embargo, esto todavía no cubre todos los casos de esquina. ¿Qué pasa si el generador exterior está cerrado? ¿Qué pasa con el caso en el que el subgenerador devuelve un valor (sí, en Python 3.3+, los generadores pueden devolver valores), cómo debería propagarse el valor de retorno? Ese yield from maneja de forma transparente todas las maletas de las esquinas es realmente impresionante. yield from simplemente funciona mágicamente y maneja todos esos casos.

Yo personalmente siento yield from es una mala elección de palabra clave porque no hace que el bidireccional naturaleza aparente. Se propusieron otras palabras clave (como delegate pero fueron rechazados porque agregar una nueva palabra clave al idioma es mucho más difícil que combinar las existentes.

En resumen, es mejor pensar en yield from como un transparent two way channel entre la persona que llama y el subgenerador.

Referencias:

  1. PEP 380 – Sintaxis para delegar a un subgenerador (Ewing) [v3.3, 2009-02-13]
  2. PEP 342 – Corutinas a través de generadores mejorados (GvR, Eby) [v2.5, 2005-05-10]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

iterative operations on mapreduce

Apache Spark – RDD

L9fWqwCdKZfrtWtvbnWBUU 1200 80

Days Gone DLC comienza ahora con la actualización gratuita del modo de supervivencia