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 gequivale afor v in g: yield vni siquiera comienza a hacer justicia a lo queyield fromse trata. Porque, seamos sinceros, si todo lo queyield fromhace es expandir elforciclo, entonces no garantiza agregaryield fromal lenguaje e impide que se implementen un montón de nuevas características en Python 2.x.Lo que
yield fromhace 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 gpodrí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 fromhacerlo.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
writerque 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
StopIterationcuando se agota el bucle for. Evidentemente, solo hacerfor x in coro: yield xno 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
writermaneja aySpamExceptionse 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 fromtransparente 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 frommaneja de manera transparente todas las esquinas es realmente impresionante .yield fromsimplemente mágicamente funciona y maneja todos esos casos.Personalmente, creo que
yield fromes una mala elección de palabras clave porque no hace evidente la naturaleza bidireccional . Se propusieron otras palabras clave (comodelegatepero 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 fromcomotransparent two way channelentre la persona que llama y el sub-generador.Referencias
fuente
except StopIteration: passDENTRO delwhile True:bucle no es una representación precisa deyield from coro- que no es un bucle infinito y después de quecorose agota (es decir, plantea StopIteration),writer_wrapperejecutará la siguiente instrucción. Después de la última declaración, se elevará automáticamenteStopIterationcomo cualquier generador agotado ...writercontenido enfor _ in range(4)lugar dewhile True, luego de imprimirlo>> 3, TAMBIÉN se elevará automáticamenteStopIterationy esto se manejará automáticamenteyield fromy luegowriter_wrapperse elevará automáticamenteStopIterationy, comowrap.send(i)no está dentro deltrybloque, 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 fromno 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 fromesto, 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
yieldy__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 fromembargo, 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_xxxson generadores simples con una sola líneafor x in input_param: yield int(x)y los otros dos respectivamente constryfloatDondequiera que se invoca un generador dentro de un generador necesita una "bomba" para volver a
yieldlos 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_generatorse usa donde haya escritoforantes el ciclo explícito . Sin embargo, no es simplemente azúcar sintáctico: maneja todos los casos de esquina que elforbucle 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
yieldde 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 fromsintaxis 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 fromlos casos de uso: obtener valor de otro generadorfuente
print(*flatten([1, [2], [3, [4]]]))yield frombá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()sonyieldcaracterí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.chainfunción porqueitertools.chainya existe. Usoyield from itertools.chain(*iters).En el uso aplicado para la rutina de E / S asíncrona ,
yield fromtiene un comportamiento similar alawaitde una función de rutina . Ambos se utilizan para suspender la ejecución de la rutina.yield fromes usado por la corutina basada en generador .awaitSe utiliza para laasync defcorutina. (desde Python 3.5+)Para Asyncio, si no es necesario admitir una versión anterior de Python (es decir,> 3.5),
async def/awaites la sintaxis recomendada para definir una rutina. Poryield fromlo 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_digitsdevuelve 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 fromhace que el código sea más simple y limpio.fuente
En pocas palabras,
yield fromproporciona la recursividad de cola para las funciones de iterador.fuente