Me está costando mucho asimilar PEP 380 .
- ¿En qué situaciones es útil "rendir de"?
- ¿Cuál es el caso de uso clásico?
- ¿Por qué se compara con microhilos?
[actualización]
Ahora entiendo la causa de mis dificultades. He usado generadores, pero nunca he usado corutinas (introducidas por PEP-342 ). A pesar de algunas similitudes, los generadores y las corutinas son básicamente dos conceptos diferentes. Comprender las rutinas (no solo los generadores) es la clave para comprender la nueva sintaxis.
Las corutinas de la OMI son la característica más oscura de Python , la mayoría de los libros hacen que parezca inútil y poco interesante.
Gracias por las excelentes respuestas, pero gracias especiales a agf y su comentario que vincula a las presentaciones de David Beazley . David rocas.
Respuestas:
Primero saquemos una cosa del camino. La explicación que
yield from g
equivale afor v in g: yield v
ni siquiera comienza a hacer justicia a lo queyield from
se trata. Porque, seamos sinceros, si todo lo queyield from
hace es expandir elfor
ciclo, entonces no garantiza agregaryield from
al lenguaje e impide que se implementen un montón de nuevas características en Python 2.x.Lo que
yield from
hace es establecer 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 excepciones).
La conexión es "bidireccional" en el sentido de que los datos pueden enviarse desde y hacia 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 de servidor" ) .Por cierto, si no está seguro de lo que significa enviar datos a un generador , primero debe descartar todo y leer sobre las rutinas ; son muy útiles (contrastarlas con las subrutinas ), pero desafortunadamente menos conocidas en Python. El curioso curso de Dave Beazley sobre Coroutines es un excelente comienzo. Lea las diapositivas 24-33 para obtener una introducción rápida.
Lectura de datos de un generador usando rendimiento de
En lugar de iterar manualmente
reader()
, podemos simplementeyield from
hacerlo.Eso funciona, y eliminamos una línea de código. Y probablemente la intención es un poco más clara (o no). Pero nada cambia la vida.
Envío de datos a un generador (corutina) usando el rendimiento de - Parte 1
Ahora hagamos algo más interesante. Creemos una rutina llamada
writer
que acepte los datos que se le envíen y escriba en un socket, fd, etc.Ahora la pregunta es, ¿cómo debe manejar la función de contenedor enviar datos al escritor, de modo que cualquier información que se envíe al contenedor se envíe de forma transparente al
writer()
?El reiniciador debe aceptar los datos que se le envían (obviamente) y también debe manejar
StopIteration
cuando se agota el bucle for. Evidentemente, solo hacerfor x in coro: yield x
no servirá. Aquí hay una versión que funciona.O podríamos hacer esto.
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 desde - Parte 2 - Manejo de excepciones
Hagámoslo más complicado. ¿Qué pasa si nuestro escritor necesita manejar excepciones? Digamos que
writer
maneja aySpamException
se imprime***
si encuentra uno.¿Qué pasa si no cambiamos
writer_wrapper
? ¿Funciona? IntentemosUm, no está funcionando porque
x = (yield)
solo aumenta la excepción y todo se detiene. Hagamos que funcione, pero manejando manualmente las excepciones y enviándolas o arrojándolas al subgenerador (writer
)Esto funciona.
¡Pero eso también!
El
yield from
transparente se encarga de enviar los valores o arrojar valores al subgenerador.Sin embargo, esto todavía no cubre todos los casos de esquina. ¿Qué sucede si el generador externo está cerrado? ¿Qué pasa con el caso cuando el subgenerador devuelve un valor (sí, en Python 3.3+, los generadores pueden devolver valores), ¿cómo debe propagarse el valor devuelto? Eso
yield from
maneja de manera transparente todas las esquinas es realmente impresionante .yield from
simplemente mágicamente funciona y maneja todos esos casos.Personalmente, creo que
yield from
es una mala elección de palabras clave porque no hace evidente la naturaleza bidireccional . Se propusieron otras palabras clave (comodelegate
pero fueron rechazadas porque agregar una nueva palabra clave al idioma es mucho más difícil que combinar las existentes).En resumen, lo mejor es pensar en
yield from
comotransparent two way channel
entre la persona que llama y el sub-generador.Referencias
fuente
except StopIteration: pass
DENTRO delwhile True:
bucle no es una representación precisa deyield from coro
- que no es un bucle infinito y después de quecoro
se agota (es decir, plantea StopIteration),writer_wrapper
ejecutará la siguiente instrucción. Después de la última declaración, se elevará automáticamenteStopIteration
como cualquier generador agotado ...writer
contenido enfor _ in range(4)
lugar dewhile True
, luego de imprimirlo>> 3
, TAMBIÉN se elevará automáticamenteStopIteration
y esto se manejará automáticamenteyield from
y luegowriter_wrapper
se elevará automáticamenteStopIteration
y, comowrap.send(i)
no está dentro deltry
bloque, en realidad se elevará en este punto ( es decir, el rastreo solo informará la línea conwrap.send(i)
, no nada desde el interior del generador)Cada situación en la que tienes un bucle como este:
Como describe la PEP, este es un intento bastante ingenuo de usar el subgenerador, le faltan varios aspectos, especialmente el manejo adecuado de los mecanismos
.throw()
/.send()
/.close()
introducidos por la PEP 342 . Para hacer esto correctamente, es necesario un código bastante complicado .Tenga en cuenta que desea extraer información de una estructura de datos recursiva. Digamos que queremos obtener todos los nodos de hoja en un árbol:
Aún más importante es el hecho de que hasta el momento
yield from
no había un método simple para refactorizar el código del generador. Supongamos que tiene un generador (sin sentido) como este:Ahora decides factorizar estos bucles en generadores separados. Sin
yield from
esto, esto es feo, hasta el punto en que pensarás dos veces si realmente quieres hacerlo. Conyield from
, en realidad es agradable mirar:Creo que de lo que habla esta sección del PEP es que cada generador tiene su propio contexto de ejecución aislado. Junto con el hecho de que la ejecución se cambia entre el generador-iterador y la persona que llama usando
yield
y__next__()
, respectivamente, esto es similar a los hilos, donde el sistema operativo cambia el hilo de ejecución de vez en cuando, junto con el contexto de ejecución (pila, registros, ...)El efecto de esto también es comparable: tanto el generador-iterador como el llamador progresan en su estado de ejecución al mismo tiempo, sus ejecuciones están intercaladas. Por ejemplo, si el generador hace algún tipo de cálculo y la persona que llama imprime los resultados, los verá tan pronto como estén disponibles. Esta es una forma de concurrencia.
Sin
yield from
embargo, esa analogía no es algo específico : es más bien una propiedad general de los generadores en Python.fuente
get_list_values_as_xxx
son generadores simples con una sola líneafor x in input_param: yield int(x)
y los otros dos respectivamente constr
yfloat
Dondequiera que se invoca un generador dentro de un generador necesita una "bomba" para volver a
yield
los valores:for v in inner_generator: yield v
. Como señala el PEP, existen complejidades sutiles que la mayoría de la gente ignora. El control de flujo no localthrow()
es un ejemplo dado en el PEP. La nueva sintaxisyield from inner_generator
se usa donde haya escritofor
antes el ciclo explícito . Sin embargo, no es simplemente azúcar sintáctico: maneja todos los casos de esquina que elfor
bucle ignora . Ser "azucarado" alienta a las personas a usarlo y así obtener los comportamientos correctos.Este mensaje en el hilo de discusión habla sobre estas complejidades:
No puedo hablar de una comparación con microhilos, aparte de observar que los generadores son un tipo de paralelismo. Puede considerar que el generador suspendido es un subproceso que envía valores a través
yield
de un subproceso de consumidor. La implementación real puede no ser nada como esto (y la implementación real obviamente es de gran interés para los desarrolladores de Python), pero esto no concierne a los usuarios.La nueva
yield from
sintaxis no agrega ninguna capacidad adicional al lenguaje en términos de subprocesos, simplemente facilita el uso correcto de las funciones existentes. O, más precisamente, facilita que un consumidor novato de un generador interno complejo escrito por un experto pase por ese generador sin romper ninguna de sus características complejas.fuente
Un breve ejemplo lo ayudará a comprender uno de
yield from
los casos de uso: obtener valor de otro generadorfuente
print(*flatten([1, [2], [3, [4]]]))
yield from
básicamente encadena iteradores de manera eficiente:Como puede ver, elimina un bucle de Python puro. Eso es casi todo lo que hace, pero encadenar iteradores es un patrón bastante común en Python.
Los subprocesos son básicamente una característica que le permite saltar de funciones en puntos completamente aleatorios y volver al estado de otra función. El supervisor de subprocesos hace esto muy a menudo, por lo que el programa parece ejecutar todas estas funciones al mismo tiempo. El problema es que los puntos son aleatorios, por lo que debe usar el bloqueo para evitar que el supervisor detenga la función en un punto problemático.
Los generadores son bastante similares a los hilos en este sentido: le permiten especificar puntos específicos (siempre que sean
yield
) donde puede saltar dentro y fuera. Cuando se usan de esta manera, los generadores se llaman corutinas.Lea estos excelentes tutoriales sobre corutinas en Python para obtener más detalles.
fuente
throw()/send()/close()
sonyield
características queyield from
, evidentemente, ha de aplicar correctamente ya que se supone que es simplificar el código. Tales trivialidades no tienen nada que ver con el uso.chain
función porqueitertools.chain
ya existe. Usoyield from itertools.chain(*iters)
.En el uso aplicado para la rutina de E / S asíncrona ,
yield from
tiene un comportamiento similar alawait
de una función de rutina . Ambos se utilizan para suspender la ejecución de la rutina.yield from
es usado por la corutina basada en generador .await
Se utiliza para laasync def
corutina. (desde Python 3.5+)Para Asyncio, si no es necesario admitir una versión anterior de Python (es decir,> 3.5),
async def
/await
es la sintaxis recomendada para definir una rutina. Poryield from
lo tanto, ya no es necesario en una rutina.Sin embargo, en las afueras general de asyncio,
yield from <sub-generator>
tiene todavía algún otro uso en la iteración de la sub-generador como se menciona en la respuesta anterior.fuente
Este código define una función que
fixed_sum_digits
devuelve un generador que enumera los números de seis dígitos de modo que la suma de dígitos sea 20.Intenta escribirlo sin
yield from
. Si encuentra una manera efectiva de hacerlo, hágamelo saber.Creo que para casos como este: visitar árboles,
yield from
hace que el código sea más simple y limpio.fuente
En pocas palabras,
yield from
proporciona la recursividad de cola para las funciones de iterador.fuente