python: ¿qué hace el rendimiento?

Desde Python 2.5 existe la posibilidad de send() , throw() , close() en un generador. Dentro del generador definido, uno puede “capturar” los datos enviados haciendo algo como:

 def gen(): while True: x = (yield) if x == 3: print('received 3!!') break else: yield x 

Lo que estoy tratando de jugar es hacer algo como:

 def gen2(): while True: yield (yield) 

Noté que es un generador legal que hace algo … Lo primero que estoy tratando de averiguar es:

¿Hay un buen uso para tal escritura?

También al hacer algo como:

 g = gen2() next(g) g.send(10) # output: 10 g.send(2) # output: nothing g.send(3) # output: 3 g.send(44) # output: nothing 

¿Por qué cada segundo ‘enviar’ no hace nada?

yield (yield) primero rinde None del yield interno. A continuación, recibe un valor de send o next . El yield interno se evalúa a este valor recibido, y el yield externo yield rápidamente ese valor.


Cada yield tiene conceptualmente dos partes:

  1. Transmitir un valor a la persona que llama de send o next .
  2. Reciba un valor del próximo send o next llamada.

Del mismo modo, cada send o next conceptualmente tiene dos partes:

  1. Transmita un valor a la expresión de yield que el generador está actualmente en pausa. (Este valor es None para el next .)
  2. Recibe un valor de la siguiente expresión de yield .

La parte más confusa del sistema es probablemente que estas partes están escalonadas. Las dos partes de un yield corresponden a dos invocaciones diferentes de send o next , y las dos partes de un send o next corresponden a dos yield diferentes s.

Si trabajamos a través de un simple ejemplo:

 def gen(): print('Not ran at first') yield (yield) g = gen() # Step 1 print(next(g)) # Step 2 print(g.send(1) # Step 3 g.send(2) # Step 4 

Así es como funcionan las cosas:

 Inside the generator Outside the generator 

Paso 1

  g calls gen() g returns a generator object without executing the print just yet statement. >>> g  

Paso 2

  next(g) sends None to g g receives None, ignores it (since it is paused at the start of the function) g prints ('not ran at first') g executes the "transmit" phase of the inner yield, transmitting None next(g) receives None 

Paso 3

  g.send(1) sends 1 to g g executes the "receive" phase of the inner yield, receiving 1 g executes the "transmit" phase of the outer yield, transmitting 1 g.send(1) receives 1 from g 

Etapa 4

  g.send(2) sends 2 to g g executes the "receive" phase of the outer yield, receiving 2 g reaches the end of gen and raises a StopIteration g.send(2) raises the StopIteration from g 

yield es una expresión. El valor de la expresión es el valor de lo que se envió usando .send , o None si no se envió nada (incluido si se usó next lugar de .send ). .send es una llamada al método y, por lo tanto, también devuelve un valor, que es el valor generado por el generador. En otras palabras, cada vez que .send , se .send un valor (que puede ser Ninguno), y cada vez que .send , se .send un valor (que puede ser Ninguno).

Aquí hay un ejemplo simple:

 def gen(): sent1 = yield 1 print(sent1, "was sent") sent2 = yield 2 print(sent2, "was sent") print("Reached end of generator") g = gen() print(next(g), "was yielded") print(g.send("A"), "was yielded") print(g.send("B"), "was yielded") next(g) # output 1 was yielded A was sent 2 was yielded B was sent Reached end of generator # StopIteration is raised here 

En su ejemplo, el primer next rendimiento Ninguno, ya que el primer yield es el rendimiento interno en el yield (yield) (es decir, el que está entre paréntesis). El primer send pasa 10 como el valor de este yield . Cada valor posterior que send convierte en el valor de uno de los rendimientos. La razón por la que algunas de sus llamadas de send no producen resultados es que el rendimiento interno no especifica ningún valor, por lo que no genera None . Como se mencionó anteriormente, cuando se llama a send , se produce un valor; en su caso, ese valor es Ninguno para el rendimiento interno, por lo que no se muestra ningún resultado en el indicador interactivo. El rendimiento externo, por otro lado, sí especifica un valor, es decir, el resultado del rendimiento interno. Por lo tanto, cuando send un valor al yield interno, se obtendrá mediante el yield externo en la siguiente iteración. (Supongo que se refiere a la salida en el indicador interactivo; si ejecuta su código como una secuencia de comandos, no habrá ninguna salida, ya que nunca print nada ni produce una salida explícita).

Aquí hay otro ejemplo que puede ser iluminador:

 def gen(): yield (yield (yield (yield "WHOA"))) >>> g = gen() >>> next(g) 'WHOA' >>> g.send(1) 1 >>> g.send(2) 2 >>> g.send(3) 3 >>> g.send(4) Traceback (most recent call last): File "", line 1, in  g.send(4) StopIteration 

Observe que cada vez que se envía un valor, se devuelve de inmediato. Esto se debe a que cada yield produce el valor de un rendimiento más profundamente nested. Cada yield “se convierte” en el valor enviado y se produce inmediatamente por el siguiente yield en la cadena. Esto continúa hasta que se agotan todos los rendimientos y se levanta StopIteration.

Preguntas similares sobre esto han sido hechas antes. Mi impresión es que la confusión tiende a surgir porque la gente espera send “simplemente” enviar un valor. Pero ese no es el caso. El uso de send avanza el generador y produce el siguiente resultado, al igual que el next . Puede pensar en next(gen) como equivalente a gen.send(None) .