Propósito de la función de "envío" del generador de Python?

165

¿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?

Tommy
fuente
3
duplicado: stackoverflow.com/questions/12637768/…
Bas Swinckels
3
Se agregó otro ejemplo de la vida real (lectura de FTP) cuando las devoluciones de llamada se convierten en un generador utilizado desde adentro
Jan Vlcinsky
2
Vale la pena mencionar que "cuando send()se llama para iniciar el generador, debe llamarse Nonecomo 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.
Rick

Respuestas:

147

Se utiliza para enviar valores a un generador que acaba de producir. Aquí hay un ejemplo explicativo artificial (no útil):

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

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:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

Lo que sucede es que takesTwoSeconds()devuelve a Deferred, 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 la doStuff()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:

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

Es mucho más complicado y difícil de manejar.

Claudiu
fuente
2
¿Puedes explicar cuál es el propósito de esto? ¿Por qué no se puede volver a crear esto con dobles entradas (número de inicio) y rendimiento?
Tommy
@Tommy: oh, porque los valores que obtuviste no tienen nada que ver con el anterior. déjame cambiar el ejemplo
Claudiu
¿Por qué usarías esto sobre una función simple que duplica su entrada?
Tommy
44
@ Tommy: No lo harías. El primer ejemplo es solo para explicar lo que hace. El segundo ejemplo es para un caso de uso realmente útil.
Claudiu
1
@Tommy: Diría que si realmente quieres saber, mira esta presentación y trabaja en todo. Una respuesta corta no será suficiente porque entonces solo dirás "¿Pero no puedo hacerlo así?" etc.
Claudiu
96

Esta función es escribir corutinas

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

huellas dactilares

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

¿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

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

Pero con el envío, se convierte en una calle de doble sentido.

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

Lo que abre la puerta al usuario personalizando el comportamiento de los generadores sobre la marcha y el generador responde al usuario.

Daniel Gratzer
fuente
3
pero una función generadora puede tomar parámetros. ¿Cómo va "Enviar" más allá de enviar un parámetro al generador?
Tommy
13
@Tommy Porque no puedes cambiar los parámetros a un generador mientras se ejecuta. Le das parámetros, se ejecuta, listo. Con enviar, le das parámetros, se ejecuta un poco, le envías un valor y hace algo diferente, repite
Daniel Gratzer
2
@Tommy Esto reiniciará el generador, lo que hará que rehagas mucho trabajo
Daniel Gratzer
55
¿Podría explicar el propósito de enviar un Ninguno antes de todo?
Shubham Aggarwal
2
@ShubhamAggarwal Se hace para 'iniciar' el generador. Es solo algo que hay que hacer. Tiene sentido cuando lo piensa, ya que la primera vez que llama send()al generador aún no ha alcanzado la palabra clave yield.
Michael
50

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:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

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:

def double_number(number):
    while True:
        number *= 2
        number = yield number

Esto es lo que parece, ya que puede ver que el envío de un nuevo valor para el número cambia el resultado:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

También puede poner esto en un bucle for como tal:

for x in range(10):
    n = c.send(n)
    print n

Para obtener más ayuda, echa un vistazo a este gran tutorial .

radtek
fuente
12
Esta comparación entre una función que no se ve afectada por send () con una que sí, realmente ayudó. ¡Gracias!
Manas Bajaj
¿Cómo puede ser esto un ejemplo ilustrativo del propósito de send? Un simple lambda x: x * 2hace lo mismo de una manera mucho menos complicada.
user209974
¿Utiliza enviar? Ve y agrega tu respuesta.
Radtek
17

Algunos casos de uso para usar generador y send()

Generadores con send()permiso:

  • recordando el estado interno de la ejecución
    • en qué paso estamos
    • ¿Cuál es el estado actual de nuestros datos?
  • secuencia de valores de retorno
  • recibir secuencia de entradas

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:

  • crear una watched_attemptinstancia de la receta
  • deja que obtenga algunas entradas
  • con cada entrada devuelve información sobre lo que está actualmente en el bote
  • con cada verificación de entrada, que la entrada es la esperada (y falla si no lo es)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot

Para usarlo, primero cree la watched_attemptinstancia:

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

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:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

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.

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

Darse cuenta de:

  • hay una secuencia lineal de pasos esperados
  • los pasos pueden diferir (algunos se están eliminando, otros se están agregando a la olla)
  • Logramos hacer todo eso mediante un generador de funciones, sin necesidad de utilizar clases complejas o estructuras similares.

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).

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

La salida se vería así:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)
Jan Vlcinsky
fuente
3
Ejecuto su ejemplo y en python 3 parece que la vigilancia_attempt.next () tiene que ser reemplazada por la siguiente (watch_attempt).
thanos.a
15

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

Python evalúa expresiones de izquierda a derecha. Observe que al evaluar una tarea, el lado derecho se evalúa antes que el lado izquierdo.

Entonces, una expresión a = bdel lado derecho se evalúa primero.

Como lo siguiente demuestra que a[p('left')] = p('right')el lado derecho se evalúa primero.

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

¿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_vallo 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étodo

6.2.9. Expresiones de rendimiento

El valor de la expresión de rendimiento después de reanudar depende del método que reanudó la ejecución. Si __next__()se utiliza (generalmente a través de un for o el next()incorporado), el resultado es Ninguno. De lo contrario, si send()se usa, el resultado será el valor pasado a ese método.

usuario2059857
fuente
13

El sendmé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.

Jochen Ritzel
fuente
6

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 yieldpalabra 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 yieldpalabra clave, mientras que el extremo derecho es el generador y es accesible a través del sendFunción del generador . Como interfaces singulares a sus respectivos extremos de la tubería, yieldy sendcumplen una doble función: cada uno empuja y tira valores hacia / desde sus extremos de la tubería, yieldempujando hacia la derecha y hacia la izquierda mientras sendhace lo contrario. Este doble deber es el quid de la confusión que rodea la semántica de declaraciones como x = yield y. Romper yieldy senddescender en dos pasos explícitos de empujar / tirar hará que su semántica sea mucho más clara:

  1. Supongamos que ges el generador. g.sendempuja un valor hacia la izquierda a través del extremo derecho de la tubería.
  2. Ejecución dentro del contexto de gpausas, permitiendo que el cuerpo de la función del generador se ejecute.
  3. El valor empujado por g.sendes arrastrado hacia la izquierda yieldy recibido en el extremo izquierdo de la tubería. En x = yield y, xse asigna al valor extraído.
  4. La ejecución continúa dentro del cuerpo de la función del generador hasta que yieldse alcanza la siguiente línea que contiene .
  5. yieldempuja un valor hacia la derecha a través del extremo izquierdo de la tubería, hacia arriba g.send. En x = yield y, yse empuja hacia la derecha a través de la tubería.
  6. La ejecución dentro del cuerpo de la función del generador se detiene, permitiendo que el alcance externo continúe donde lo dejó.
  7. g.send reanuda y extrae el valor y lo devuelve al usuario.
  8. La g.sendpró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 que next(g)es la abreviatura, se llama por primera vez (es ilegal pasar algo que no sea Nonea la primera sendllamada). Y puede tener un final: cuando no hay más yielddeclaraciones que alcanzar en el cuerpo de la función del generador.

¿Ves lo que hace que la yielddeclaración (o más exactamente, los generadores) sea tan especial? A diferencia de la returnpalabra clave miserable , yieldpuede 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 la returnpalabra clave). Cuando yieldse 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. Y sendes 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, yieldy sendson dos lados del mismo tubo de monedas :

  1. right_end.push(None) # the first half of g.send; sending None is what starts a generator
  2. right_end.pause()
  3. left_end.start()
  4. initial_value = left_end.pull()
  5. if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
  6. left_end.do_stuff()
  7. left_end.push(y) # the first half of yield
  8. left_end.pause()
  9. right_end.resume()
  10. value1 = right_end.pull() # the second half of g.send
  11. right_end.do_stuff()
  12. right_end.push(value2) # the first half of g.send (again, but with a different value)
  13. right_end.pause()
  14. left_end.resume()
  15. x = left_end.pull() # the second half of yield
  16. goto 6

La transformación clave es que nos hemos dividido x = yield yy value1 = g.send(value2)cada uno en dos declaraciones: left_end.push(y)y x = left_end.pull(); y value1 = right_end.pull()y right_end.push(value2). Hay dos casos especiales de la yieldpalabra clave: x = yieldy yield y. Estos son azúcares sintácticos, respectivamente, para x = yield Noney _ = 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 a g.send(None). Con esto en mente, podemos centrarnos solo en cómo sendfunciona y hablar solo sobre el avance del generador send.

Supongamos que tenemos

def f(y):  # This is the "generator function" referenced above
    while True:
        x = yield y
        y = x
g = f(1)
g.send(None)  # yields 1
g.send(2)     # yields 2

Ahora, la definición de faproximadamente desugar a la siguiente función ordinaria (sin generador):

def f(y):
    bidirectional_pipe = BidirectionalPipe()
    left_end = bidirectional_pipe.left_end
    right_end = bidirectional_pipe.right_end

    def impl():
        initial_value = left_end.pull()
        if initial_value is not None:
            raise TypeError(
                "can't send non-None value to a just-started generator"
            )

        while True:
            left_end.push(y)
            x = left_end.pull()
            y = x

    def send(value):
        right_end.push(value)
        return right_end.pull()

    right_end.send = send

    # This isn't real Python; normally, returning exits the function. But
    # pretend that it's possible to return a value from a function and then
    # continue execution -- this is exactly the problem that generators were
    # designed to solve!
    return right_end
    impl()

Lo siguiente ha sucedido en esta transformación de f:

  1. Hemos trasladado la implementación a una función anidada.
  2. Hemos creado una tubería bidireccional cuya left_end accederá la función anidada y cuyo right_endalcance externo devolverá y accederá, right_endes lo que conocemos como el objeto generador.
  3. Dentro de la función anidada, la primera cosa que hacemos es comprobar que left_end.pull()es None, consumiendo un valor empujado en el proceso.
  4. Dentro de la función anidada, la instrucción x = yield yha sido reemplazada por dos líneas:left_end.push(y) y x = left_end.pull().
  5. Hemos definido la sendfunción para right_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, gse asigna right_endy luego impl()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:

left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end

y = 1  # from g = f(1)

# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks

# Receive the pushed value, None
initial_value = left_end.pull()

if initial_value is not None:  # ok, `g` sent None
    raise TypeError(
        "can't send non-None value to a just-started generator"
    )

left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off

# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()

# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes

# Receive the pushed value, 2
x = left_end.pull()
y = x  # y == x == 2

left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off

# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()

x = left_end.pull()
# blocks until the next call to g.send

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:

def f1(x):
    while True:
        x = yield x

def f2():  # No parameter
    while True:
        x = yield x

En su mayor parte, se desugaron de la misma manera que f, las únicas diferencias son cómo yieldse transforman las declaraciones:

def f1(x):
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end


def f2():
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end

En el primero, el valor pasado a f1se empuja (cede) inicialmente, y luego todos los valores extraídos (enviados) se empujan (ceden) de regreso. En el segundo, xno tiene ningún valor (todavía) cuando llega por primera vez push, por lo que UnboundLocalErrorse eleva un.

BallpointBen
fuente
"El argumento 1 en g = f (1) ha sido capturado normalmente y asignado a y dentro del cuerpo de f, pero el verdadero Verdadero aún no ha comenzado". Por qué no? ¿Por qué Python no intentaría ejecutar este código hasta que encuentre, por ejemplo yield?
Josh
@ Josh El cursor no avanza hasta la primera llamada a send; se necesita una llamada de send(None)para mover el cursor a la primera yieldinstrucción, y solo entonces las sendllamadas posteriores realmente envían un valor "real" yield.
BallpointBen
Gracias. Eso es interesante, ¿entonces el intérprete sabe que la función lo f hará yield en algún momento y, por lo tanto, esperará hasta que reciba sendla llamada? Con una función normal llamada, el intérprete comenzaría a ejecutar de finmediato, ¿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?
Josh
@Josh Construí este modelo mental simplemente observando cómo funcionan los diferentes generadores de juguetes, sin comprender las partes internas de Python. Sin embargo, el hecho de que la inicial send(None)arroje el valor apropiado (p. Ej. 1) Sin enviarlo Noneal generador sugiere que la primera llamada a sendes un caso especial. Es una interfaz difícil de diseñar; si deja que el primero sendenví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.
BallpointBen
Gracias BallpointBen. Muy interesante, dejé una pregunta aquí para ver por qué ese es el caso.
Josh
2

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) ...

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

El resultado es:

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
Peter
fuente