¿Puede alguien darme un ejemplo de por qué existe la función "enviar" asociada con la función del generador Python? Entiendo completamente la función de rendimiento. Sin embargo, la función de envío me resulta confusa. La documentación sobre este método es complicada:
generator.send(value)
Reanuda la ejecución y "envía" un valor a la función del generador. El argumento del valor se convierte en el resultado de la expresión de rendimiento actual. El método send () devuelve el siguiente valor obtenido por el generador o aumenta StopIteration si el generador sale sin generar otro valor.
Qué significa eso? ¿Pensé que el valor era la entrada a la función? La frase "El método send () devuelve el siguiente valor producido por el generador" parece ser también el propósito exacto de la función de rendimiento; rendimiento devuelve el siguiente valor producido por el generador ...
¿Puede alguien darme un ejemplo de un generador que utiliza el envío que logra algo que el rendimiento no puede?
send()
se llama para iniciar el generador, debe llamarseNone
como argumento, porque no hay una expresión de rendimiento que pueda recibir el valor", citado del documento oficial y para el cual se cita la pregunta. desaparecido.Respuestas:
Se utiliza para enviar valores a un generador que acaba de producir. Aquí hay un ejemplo explicativo artificial (no útil):
No puedes hacer esto solo con
yield
.En cuanto a por qué es útil, uno de los mejores casos de uso que he visto es el de Twisted
@defer.inlineCallbacks
. Esencialmente te permite escribir una función como esta:Lo que sucede es que
takesTwoSeconds()
devuelve aDeferred
, que es un valor que promete que un valor se calculará más adelante. Twisted puede ejecutar el cálculo en otro hilo. Cuando se realiza el cálculo, lo pasa al diferido y el valor luego se envía de vuelta a ladoStuff()
función. Por lo tanto,doStuff()
puede terminar pareciéndose más o menos a una función de procedimiento normal, excepto que puede estar haciendo todo tipo de cálculos y devoluciones de llamadas, etc. La alternativa antes de esta funcionalidad sería hacer algo como:Es mucho más complicado y difícil de manejar.
fuente
Esta función es escribir corutinas
huellas dactilares
¿Ves cómo se pasa el control de un lado a otro? Esas son las corutinas. Se pueden usar para todo tipo de cosas interesantes como asynch IO y similares.
Piénselo así, con un generador y sin envío, es una calle de sentido único
Pero con el envío, se convierte en una calle de doble sentido.
Lo que abre la puerta al usuario personalizando el comportamiento de los generadores sobre la marcha y el generador responde al usuario.
fuente
send()
al generador aún no ha alcanzado la palabra claveyield
.Esto puede ayudar a alguien. Aquí hay un generador que no se ve afectado por la función de envío. Toma el parámetro número en la instanciación y no se ve afectado por el envío:
Ahora, así es como haría el mismo tipo de función utilizando enviar, por lo que en cada iteración puede cambiar el valor de número:
Esto es lo que parece, ya que puede ver que el envío de un nuevo valor para el número cambia el resultado:
También puede poner esto en un bucle for como tal:
Para obtener más ayuda, echa un vistazo a este gran tutorial .
fuente
send
? Un simplelambda x: x * 2
hace lo mismo de una manera mucho menos complicada.Algunos casos de uso para usar generador y
send()
Generadores con
send()
permiso:Aquí hay algunos casos de uso:
Intento observado de seguir una receta
Tengamos una receta, que espera un conjunto predefinido de entradas en algún orden.
Podemos:
watched_attempt
instancia de la recetacon cada verificación de entrada, que la entrada es la esperada (y falla si no lo es)
Para usarlo, primero cree la
watched_attempt
instancia:La llamada a
.next()
es necesaria para iniciar la ejecución del generador.Se muestra el valor devuelto, nuestro bote está actualmente vacío.
Ahora realice algunas acciones siguiendo lo que la receta espera:
Como vemos, la olla finalmente está vacía.
En caso de que uno no siguiera la receta, fracasaría (lo que podría ser el resultado deseado del intento observado de cocinar algo, solo aprendiendo que no prestamos suficiente atención cuando recibimos instrucciones.
Darse cuenta de:
Totales acumulados
Podemos usar el generador para realizar un seguimiento del total acumulado de los valores que se le envían.
Cada vez que agregamos un número, se devuelve el recuento de entradas y la suma total (válido en el momento en que se envió la entrada anterior).
La salida se vería así:
fuente
El
send()
método controla cuál será el valor a la izquierda de la expresión de rendimiento.Para comprender cómo difiere el rendimiento y qué valor tiene, primero revisemos rápidamente el orden en que se evalúa el código de Python.
Sección 6.15 Orden de evaluación
Entonces, una expresión
a = b
del lado derecho se evalúa primero.Como lo siguiente demuestra que
a[p('left')] = p('right')
el lado derecho se evalúa primero.¿Qué hace el rendimiento ?, rendimiento, suspende la ejecución de la función y regresa a la persona que llama, y reanuda la ejecución en el mismo lugar que dejó antes de suspender.
¿Dónde se suspende exactamente la ejecución? Es posible que ya lo haya adivinado ... la ejecución se suspende entre el lado derecho e izquierdo de la expresión de rendimiento. Por
new_val = yield old_val
lo tanto, la ejecución se detiene en el=
signo, y el valor de la derecha (que es antes de suspender, y también es el valor devuelto a la persona que llama) puede ser algo diferente del valor de la izquierda (que es el valor que se asigna después de reanudar ejecución).yield
produce 2 valores, uno a la derecha y otro a la izquierda.¿Cómo se controla el valor en el lado izquierdo de la expresión de rendimiento? a través del
.send()
método6.2.9. Expresiones de rendimiento
fuente
El
send
método implementa corutinas .Si no ha encontrado Coroutines, es difícil entenderlo porque cambian la forma en que fluye un programa. Puedes leer un buen tutorial para más detalles.
fuente
La palabra "ceder" tiene dos significados: producir algo (p. Ej., Ceder maíz) y detenerse para permitir que alguien / otra cosa continúe (p. Ej., Autos que ceden el paso a los peatones). Ambas definiciones se aplican a la
yield
palabra clave de Python ; Lo que hace que las funciones del generador sean especiales es que, a diferencia de las funciones regulares, los valores pueden ser "devueltos" a la persona que llama mientras simplemente detiene, no termina, una función del generador.Es más fácil imaginar un generador como un extremo de una tubería bidireccional con un extremo "izquierdo" y un extremo "derecho"; esta tubería es el medio sobre el cual se envían valores entre el generador mismo y el cuerpo de la función del generador. Cada extremo de la tubería tiene dos operaciones:
push
que envía un valor y bloquea hasta que el otro extremo de la tubería extrae el valor y no devuelve nada; ypull
, que bloquea hasta que el otro extremo de la tubería empuja un valor y devuelve el valor empujado. En tiempo de ejecución, la ejecución rebota de un lado a otro entre los contextos a cada lado de la tubería: cada lado se ejecuta hasta que envía un valor al otro lado, en ese punto se detiene, deja que el otro lado se ejecute y espera un valor en volver, en cuyo punto el otro lado se detiene y se reanuda. En otras palabras, cada extremo de la tubería se ejecuta desde el momento en que recibe un valor hasta el momento en que envía un valor.La tubería es funcionalmente simétrica, pero, por convención que estoy definiendo en esta respuesta, el extremo izquierdo solo está disponible dentro del cuerpo de la función del generador y es accesible a través de la
yield
palabra clave, mientras que el extremo derecho es el generador y es accesible a través delsend
Función del generador . Como interfaces singulares a sus respectivos extremos de la tubería,yield
ysend
cumplen una doble función: cada uno empuja y tira valores hacia / desde sus extremos de la tubería,yield
empujando hacia la derecha y hacia la izquierda mientrassend
hace lo contrario. Este doble deber es el quid de la confusión que rodea la semántica de declaraciones comox = yield y
. Romperyield
ysend
descender en dos pasos explícitos de empujar / tirar hará que su semántica sea mucho más clara:g
es el generador.g.send
empuja un valor hacia la izquierda a través del extremo derecho de la tubería.g
pausas, permitiendo que el cuerpo de la función del generador se ejecute.g.send
es arrastrado hacia la izquierdayield
y recibido en el extremo izquierdo de la tubería. Enx = yield y
,x
se asigna al valor extraído.yield
se alcanza la siguiente línea que contiene .yield
empuja un valor hacia la derecha a través del extremo izquierdo de la tubería, hacia arribag.send
. Enx = yield y
,y
se empuja hacia la derecha a través de la tubería.g.send
reanuda y extrae el valor y lo devuelve al usuario.g.send
próxima vez que se llame, regrese al Paso 1.Si bien es cíclico, este procedimiento tiene un comienzo: cuándo
g.send(None)
, que es lo quenext(g)
es la abreviatura, se llama por primera vez (es ilegal pasar algo que no seaNone
a la primerasend
llamada). Y puede tener un final: cuando no hay másyield
declaraciones que alcanzar en el cuerpo de la función del generador.¿Ves lo que hace que la
yield
declaración (o más exactamente, los generadores) sea tan especial? A diferencia de lareturn
palabra clave miserable ,yield
puede pasar valores a su interlocutor y recibir valores de su interlocutor, ¡todo sin terminar la función en la que vive! (Por supuesto, si desea terminar una función, o un generador, también es útil tener lareturn
palabra clave). Cuandoyield
se encuentra una declaración, la función del generador simplemente hace una pausa y luego vuelve a subir justo donde dejó apagado al ser enviado otro valor. Ysend
es solo la interfaz para comunicarse con el interior de una función generadora desde el exterior.Si realmente queremos romper esta analogía de empujar / tirar / tubería lo más que podamos, terminamos con el siguiente pseudocódigo que realmente conduce a casa que, aparte de los pasos 1-5,
yield
ysend
son dos lados del mismo tubo demonedas:right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
La transformación clave es que nos hemos dividido
x = yield y
yvalue1 = g.send(value2)
cada uno en dos declaraciones:left_end.push(y)
yx = left_end.pull()
; yvalue1 = right_end.pull()
yright_end.push(value2)
. Hay dos casos especiales de layield
palabra clave:x = yield
yyield y
. Estos son azúcares sintácticos, respectivamente, parax = yield None
y_ = yield y # discarding value
.Para obtener detalles específicos sobre el orden preciso en que se envían los valores a través de la tubería, consulte a continuación.
Lo que sigue es un modelo concreto bastante largo de lo anterior. Primero, primero se debe tener en cuenta que para cualquier generador
g
,next(g)
es exactamente equivalente ag.send(None)
. Con esto en mente, podemos centrarnos solo en cómosend
funciona y hablar solo sobre el avance del generadorsend
.Supongamos que tenemos
Ahora, la definición de
f
aproximadamente desugar a la siguiente función ordinaria (sin generador):Lo siguiente ha sucedido en esta transformación de
f
:left_end
accederá la función anidada y cuyoright_end
alcance externo devolverá y accederá,right_end
es lo que conocemos como el objeto generador.left_end.pull()
esNone
, consumiendo un valor empujado en el proceso.x = yield y
ha sido reemplazada por dos líneas:left_end.push(y)
yx = left_end.pull()
.send
función pararight_end
, que es la contraparte de las dos líneas que reemplazamosx = yield y
declaración en el paso anterior.En este mundo de fantasía donde las funciones pueden continuar después de regresar,
g
se asignaright_end
y luegoimpl()
se llama. Entonces, en nuestro ejemplo anterior, si siguiéramos la ejecución línea por línea, lo que sucedería es más o menos lo siguiente:Esto se asigna exactamente al pseudocódigo de 16 pasos anterior.
Hay algunos otros detalles, como cómo se propagan los errores y qué sucede cuando llega al final del generador (la tubería está cerrada), pero esto debería aclarar cómo funciona el flujo de control básico cuando
send
se usa.Usando estas mismas reglas de desagüe, veamos dos casos especiales:
En su mayor parte, se desugaron de la misma manera que
f
, las únicas diferencias son cómoyield
se transforman las declaraciones:En el primero, el valor pasado a
f1
se empuja (cede) inicialmente, y luego todos los valores extraídos (enviados) se empujan (ceden) de regreso. En el segundo,x
no tiene ningún valor (todavía) cuando llega por primera vezpush
, por lo queUnboundLocalError
se eleva un.fuente
yield
?send
; se necesita una llamada desend(None)
para mover el cursor a la primerayield
instrucción, y solo entonces lassend
llamadas posteriores realmente envían un valor "real"yield
.f
haráyield
en algún momento y, por lo tanto, esperará hasta que recibasend
la llamada? Con una función normal llamada, el intérprete comenzaría a ejecutar def
inmediato, ¿verdad? Después de todo, no hay compilación AOT de ningún tipo en Python. ¿Estás seguro de que ese es el caso? (sin cuestionar lo que estás diciendo, estoy realmente desconcertado por lo que escribiste aquí). ¿Dónde puedo leer más sobre cómo Python sabe que necesita esperar antes de comenzar a ejecutar el resto de la función?send(None)
arroje el valor apropiado (p. Ej.1
) Sin enviarloNone
al generador sugiere que la primera llamada asend
es un caso especial. Es una interfaz difícil de diseñar; si deja que el primerosend
envíe un valor arbitrario, el orden de los valores cedidos y los valores enviados sería uno por uno en comparación con lo que es actualmente.Estos también me confundieron. Aquí hay un ejemplo que hice al tratar de configurar un generador que produce y acepta señales en orden alternativo (rendimiento, aceptación, rendimiento, aceptación) ...
El resultado es:
fuente