Python 3: método de envío de generadores.

No puedo entender el método de send . Entiendo que se utiliza para operar el generador. Pero la syntax está aquí: generator.send(value) .

De alguna manera no puedo entender por qué el valor debería convertirse en el resultado de la expresión de yield actual. Preparé un ejemplo:

 def gen(): for i in range(10): X = yield i if X == 'stop': break print("Inside the function " + str(X)) m = gen() print("1 Outside the function " + str(next(m)) + '\n') print("2 Outside the function " + str(next(m)) + '\n') print("3 Outside the function " + str(next(m)) + '\n') print("4 Outside the function " + str(next(m)) + '\n') print('\n') print("Outside the function " + str(m.send(None)) + '\n') # Start generator print("Outside the function " + str(m.send(77)) + '\n') print("Outside the function " + str(m.send(88)) + '\n') #print("Outside the function " + str(m.send('stop')) + '\n') print("Outside the function " + str(m.send(99)) + '\n') print("Outside the function " + str(m.send(None)) + '\n') 

El resultado es:

 1 Outside the function 0 Inside the function None 2 Outside the function 1 Inside the function None 3 Outside the function 2 Inside the function None 4 Outside the function 3 Inside the function None Outside the function 4 Inside the function 77 Outside the function 5 Inside the function 88 Outside the function 6 Inside the function 99 Outside the function 7 Inside the function None Outside the function 8 

Bueno, francamente hablando, me asombra.

  1. En la documentación podemos leer que cuando se ejecuta una statement de yield , el estado del generador se congela y el valor de expression_list se devuelve a la next persona que llama. Bueno, no parece que haya sucedido. ¿Por qué podemos ejecutar la función if e print dentro de gen() ?
  2. ¿Cómo puedo entender por qué X difiere dentro y fuera de la función? De acuerdo. Supongamos que el send(77) transmite 77 en m . Bueno, la expresión de yield convierte en 77. Entonces, ¿qué es X = yield i ? ¿Y cómo 77 dentro de la función se convierte en 5 cuando ocurre afuera?
  3. ¿Por qué la primera cadena de resultados no refleja nada de lo que ocurre dentro del generador?

De todos modos, ¿podría comentar de alguna manera estas declaraciones de send y yield ?

Cuando usas el yield send y expresión en un generador, lo tratas como una coroutina; un subproceso de ejecución separado que puede ejecutarse intercalado secuencialmente pero no en paralelo con su llamador.

Cuando el llamante ejecuta R = m.send(a) , coloca el objeto a en la ranura de entrada del generador, transfiere el control al generador y espera una respuesta. El generador recibe el objeto a como el resultado de X = yield i , y se ejecuta hasta que llega a otra expresión de yield , por ejemplo, Y = yield j . Luego coloca j en su ranura de salida, transfiere el control al llamante y espera hasta que se reanude nuevamente. La persona que llama recibe j como resultado de R = m.send(a) y se ejecuta hasta que llega a otra S = m.send(b) , y así sucesivamente.

R = next(m) es igual que R = m.send(None) ; está poniendo None en la ranura de entrada del generador, por lo que si el generador verifica el resultado de X = yield i entonces X será None .

Como metáfora, considérese un camarero tonto :

Camarero tonto

Cuando el servidor recibe un pedido de un cliente, ponen la almohadilla en el camarero tonto, la send a la cocina y esperan por la escotilla para el plato:

 R = kitchen.send("Ham omelette, side salad") 

El chef (que ha estado esperando junto a la escotilla) recoge el pedido, prepara el plato, lo yield al restaurante y espera el siguiente pedido:

 next_order = yield [HamOmelette(), SideSalad()] 

El servidor (que ha estado esperando por la compuerta) lleva el plato al cliente y lo devuelve con otro pedido, etc.

Debido a que tanto el servidor como el chef esperan junto a la escotilla después de send un pedido o yield un plato, solo hay una persona que hace algo al mismo tiempo, es decir, el proceso es de un solo hilo. Ambos lados pueden usar el flujo de control normal, ya que la maquinaria del generador (el camarero tonto) se encarga de la intercalación.

La parte más confusa debe ser esta línea X = yield i , especialmente cuando se llama a send() en el generador. En realidad, lo único que necesitas saber es:

en el nivel léxico: next() es igual a send(None)

en el nivel de intérprete: X = yield i es igual a las líneas por debajo ( PEDIDOS DE PEDIDO ):

 yield i # won't continue until next() or send() is called # and this is also the entry point of next() or send() X = the_input_of_send 

y, las 2 líneas de comentario son la razón exacta, por la que debemos llamar a send(None) por primera vez, porque el generador devolverá i (rendimiento i ) antes de asignar el valor a X

 def gen(): i = 1 while True: i += 1 x = yield i print(x) m = gen() next(m) next(m) m.send(4) 

resultado

 None 4 

mira los códigos más simplificados arriba.
Creo que lo que condujo a su confusión es la statement ‘x = rendimiento i’, esta statement no dice que el valor aceptado por el método send () se asoció a i y luego se asignó a x. En cambio, el valor i se devuelve por la statement de rendimiento al generador, x se asocia por el método send (). Una statement hace dos cosas al mismo tiempo.

Dado que incluso solicitó comentarios, considere el siguiente caso:

 def lambda_maker(): def generator(): value = None while 1: value = yield value value= value[0][1] f = generator() next(f) # skip the first None return f.send # a handy lambda value: value[0][1] 

Ahora las siguientes dos líneas son equivalentes:

 a_list.sort(key=lambda a: a[0][1]) a_list.sort(key=lambda_maker()) 

(Por cierto, en las implementaciones actuales de CPython2 y CPython3 (2018-05-26, día 1 post-GDPR ☺), la segunda línea se ejecuta más rápido que la primera, pero ese es un detalle relacionado con la sobrecarga de inicialización de objetos de marco en cada llamada de función).

¿Qué pasa aquí? lambda_maker llama a f=generator() y obtiene un generador; llamando a la inicial next(f) comienza a ejecutar el generador y consume el valor inicial de None , y se detiene en la línea de yield . Luego devuelve el método enlazado f.send a su llamador. A partir de este momento, cada vez que se llama a este método enlazado, el generator.value local recibe el argumento del método enlazado, recalcula el value y luego vuelve a realizar el bucle, devolviendo el valor actual del value y espera a que el siguiente .send obtenga otro valor. .

El objeto generador permanece en la memoria y todo lo que hace en el bucle es:

  • ceder el resultado actual (inicialmente ninguno)
  • recibir otro valor (cualquier cosa que alguien usó como argumento para .send )
  • volver a calcular el resultado actual en función del valor recibido
  • vuelta atrás