2012-09-07 14 views
5

estoy jugando un poco con los generadores y las expresiones generadoras y no estoy completamente seguro de que entiendo cómo funcionan (some reference material):Tratar de entender el rendimiento como una expresión

>>> a = (x for x in range(10)) 
>>> next(a) 
0 
>>> next(a) 
1 
>>> a.send(-1) 
2 
>>> next(a) 
3 

lo que parece que fue generator.send ignorado Eso tiene sentido (supongo) porque no hay yield expresión explícita para captar la información enviada ...

Sin embargo,

>>> a = ((yield x) for x in range(10)) 
>>> next(a) 
0 
>>> print next(a) 
None 
>>> print next(a) 
1 
>>> print next(a) 
None 
>>> a.send(-1) #this send is ignored, Why? ... there's a yield to catch it... 
2 
>>> print next(a) 
None 
>>> print next(a) 
3 
>>> a.send(-1) #this send isn't ignored 
-1 

entiendo que esto es bastante lejos de allí, y yo (en la actualidad) puede No piense en un caso de uso para esto (así que no pregunte;)

Estoy explorando principalmente para tratar de descubrir cómo funcionan estos diversos métodos de generador (y cómo funcionan las expresiones de generador en general). ¿Por qué mi segundo ejemplo alterna entre producir un valor razonable y None? Además, ¿alguien puede explicar por qué uno de mis generator.send fue ignorado mientras que el otro no?

+1

Compruebe si este enlace puede ayudarlo ... http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained –

Respuesta

3

La confusión aquí es que la expresión del generador está haciendo un yield oculto. Aquí está en forma de función:

def foo(): 
    for x in range(10): 
     yield (yield x) 

Cuando haces un .send(), lo que pasa es que el interior yield x es ejecutado, lo que produce x. Entonces la expresión evalúa el valor de .send, y el siguiente rendimiento rinde eso. Aquí está en forma más clara:

def foo(): 
    for x in range(10): 
     sent_value = (yield x) 
     yield sent_value 

Así, la salida es muy predecible:

>>> a = foo() 
#start it off 
>>> a.next() 
0 
#execution has now paused at "sent_value = ?" 
#now we fill in the "?". whatever we send here will be immediately yielded. 
>>> a.send("yieldnow") 
'yieldnow' 
#execution is now paused at the 'yield sent_value' expression 
#as this is not assigned to anything, whatever is sent now will be lost 
>>> a.send("this is lost") 
1 
#now we're back where we were at the 'yieldnow' point of the code 
>>> a.send("yieldnow") 
'yieldnow' 
#etc, the loop continues 
>>> a.send("this is lost") 
2 
>>> a.send("yieldnow") 
'yieldnow' 
>>> a.send("this is lost") 
3 
>>> a.send("yieldnow") 
'yieldnow' 

EDIT: Ejemplo de uso. Con mucho, el más genial que he visto hasta ahora es la función inlineCallbacks de twisted. See here para un artículo que lo explica. La esencia de esto es que le permite ceder funciones para que se ejecuten en subprocesos, y una vez que se realizan las funciones, retorcidas devuelve el resultado de la función a su código. Por lo tanto, puede escribir código que dependa en gran medida de los hilos de una manera muy lineal e intuitiva, en lugar de tener que escribir toneladas de pequeñas funciones por todos lados.

Consulte el PEP 342 para obtener más información sobre la lógica de tener .send trabajando con posibles casos de uso (el ejemplo retorcido que proporcioné es un ejemplo de la ayuda a la E/S asincrónica que ofrece este cambio).

+0

Gracias, esto fue muy útil. – mgilson

+0

Hmmm ... Supongo que podría usar esto para unir 2 listas '['a', 'c', 'e']' y '['b', 'd', 'f']' en '[' a ',' b ',' c ',' d ',' e ',' f '] '... – mgilson

+0

@mgilson: también puede usar' + 'para eso. Voy a actualizar mi respuesta pronto en un buen caso de uso – Claudiu

2

Este generador se traduce en:

for i in xrange(10): 
    x = (yield i) 
    yield x 

Resultado de la segunda llamada a send()/siguiente() se ignoran, ya que no hace nada con el resultado de uno de los rendimientos.

2

Te estás confundiendo un poco porque en realidad estás generando a partir de dos fuentes: la expresión del generador (... for x in range(10)) es un generador, pero creas otra fuente con el yield. Puedes ver que si haces list(a) obtendrás [0, None, 1, None, 2, None, 3, None, 4, None, 5, None, 6, None, 7, None, 8, None, 9, None].

Su código es equivalente a esto:

>>> def gen(): 
...  for x in range(10): 
...   yield (yield x) 

Sólo el rendimiento interior ("rendimiento x") "se utiliza" en el generador --- se utiliza como el valor del rendimiento exterior. De modo que este generador itera hacia adelante y hacia atrás entre los valores de rendimiento del rango, y cede todo lo que se "envía" a esos rendimientos. Si envía algo al rendimiento interno, lo recupera, pero si envía una iteración par, el envío se envía al rendimiento externo y se ignora.

+0

No creé 2 generadores - Creé 1 generador con 2 declaraciones de rendimiento ;-) (aparentemente) – mgilson

+0

Tienes razón, una mejor manera de decirlo sería que estás generando a partir de dos fuentes. – BrenBarn

+0

Me sorprendió cuando me di cuenta de que 'yield' era una expresión (no una afirmación). Así que empecé a jugar para tratar de descubrir qué podía hacer * con ese conocimiento * ... – mgilson

-1

De hecho, el método send está diseñado para trabajar con un objeto generador que es el resultado de una co-rutina que ha escrito explícitamente. Es difícil darle algún sentido en una expresión de generador, aunque funciona.

- EDITAR - que había escrito previamente esta, pero es incorrecct, como el rendimiento dentro de las expresiones generadoras son predecibles a través de implementaciones - aunque no se menciona en ninguna PEP.

generator expressions are not meant to have the yield keyword - I am not shure the behavior is even defined in this case. We could think a little and get to what is happening on your expression, to meet from where those "None"s are coming from. However, assume that as a side effect of how the yield is implemented in Python (and probably it is even implementation dependent), not as something that should be so.

La forma correcta para una expresión generador, de forma simplificada es:

(<expr> for <variable> in <sequence> [if <expr>]) 

así, <expr> se evalúa para cada valor de la <sequence: - no sólo es yield uneeded, ya que no debería usarlo

Tanto yield y los métodos send están destinados a ser utilizados en co-rutinas completos, algo así como:

def doubler(): 
    value = 0 
    while value < 100: 
     value = 2 * (yield value) 

Y usted puede utilizarlo como:

>>> a = doubler() 
>>> # Next have to be called once, so the code will run up to the first "yield" 
... 
>>> a.next() 
0 
>>> a.send(10) 
20 
>>> a.send(20) 
40 
>>> a.send(23) 
46 
>>> a.send(51) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 
>>> 
+0

¿A quién llamar para votar? – jsbueno

+1

aye. "Las expresiones del generador no están destinadas a tener la palabra clave yield, no estoy seguro de que el comportamiento esté incluso definido en este caso". está perfectamente definido. ver [este pastebin] (http://pastebin.com/Yq6XeTUg). es un poco extraño, pero tiene sentido. – Claudiu

+0

el None no es de una implementación dependiente, solo que estaba cediendo Nones (llamando a .next() que envió None al rendimiento) – Claudiu

0

El generador que escribió es equivalente a la más detallada:

def testing(): 
    for x in range(10): 
      x = (yield x) 
      yield x 

Como puedes verla e, el segundo yield, que está implícito en la expresión del generador, no guarda el valor que usted pasa, por lo tanto, dependiendo de dónde esté bloqueada la ejecución del generador, el send puede funcionar o no.

Cuestiones relacionadas