2010-09-22 27 views
37

Estaba jugando con listas de comprensión para comprenderlas mejor y me encontré con un resultado inesperado que no puedo explicar. No he encontrado esta pregunta antes, pero si es/es/una pregunta repetida, me disculpo.Python: sintaxis avanzada de comprensión de listas anidadas

Básicamente estaba tratando de escribir un generador que genera generadores. Un generador simple que utiliza lista por comprensión se vería así:

(x for x in range(10) if x%2==0) # generates all even integers in range(10) 

Lo que estaba tratando de hacer era escribir un generador que genera dos generadores - el primero de los cuales genera los números pares en la gama (10) y el segundo de los cuales generó los números impares en el rango (10). Para esto, lo hice:

>>> (x for x in range(10) if x%2==i for i in range(2)) 
<generator object <genexpr> at 0x7f6b90948f00> 

>>> for i in g.next(): print i 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
UnboundLocalError: local variable 'i' referenced before assignment 
>>> g.next() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 
>>> g = (x for x in range(10) if x%2==i for i in range(2)) 
>>> g 
<generator object <genexpr> at 0x7f6b90969730> 
>>> g.next() 
Traceback (most recent call last): 
     File "<stdin>", line 1, in <module> 
     File "<stdin>", line 1, in <genexpr> 
    UnboundLocalError: local variable 'i' referenced before assignment 

No entiendo por qué 'i' se está haciendo referencia antes de la asignación

pensé que podría haber tenido algo que ver con i in range(2), por lo que hice:

>>> g = (x for x in range(10) if x%2==i for i in [0.1]) 
>>> g 
<generator object <genexpr> at 0x7f6b90948f00> 
>>> g.next() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
UnboundLocalError: local variable 'i' referenced before assignment 

Esto no tenía sentido para mí, así que pensé que era mejor intentar algo más simple primero. Así que volví a listas y trató:

>>> [x for x in range(10) if x%2==i for i in range(2)] 
[1, 1, 3, 3, 5, 5, 7, 7, 9, 9] 

que esperaba que ser el mismo que:

>>> l = [] 
>>> for i in range(2): 
...  for x in range(10): 
...    if x%2==i: 
...      l.append(x) 
... 
>>> l 
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] # so where is my list comprehension malformed? 

Pero cuando lo probé en una corazonada, esto funcionó:

>>> [[x for x in range(10) if x%2==i] for i in range(2)] 
[[0, 2, 4, 6, 8], [1, 3, 5, 7, 9]] # so nested lists in nested list comprehension somehow affect the scope of if statements? :S 

Así que pensé que podría ser un problema con qué nivel de alcance funciona la instrucción if. Así que probé esto:

>>> [x for x in range(10) for i in range(2) if x%2==i] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Y ahora estoy completamente confundido. ¿Alguien puede explicar este comportamiento? No entiendo por qué las listas de mi comprensión parecen estar mal formadas, ni entiendo cómo funciona el alcance de las declaraciones if.

Cualquier ayuda sería grandemente apreciado

Gracias

PD: Mientras corrección de pruebas de la pregunta, me di cuenta de que esto tiene un aspecto un poco como una pregunta tarea - no lo es.

+0

Did '[x para x en el rango (10) si x% 2 == i para i en el rango (2)] '¿trabajo? Estoy obteniendo 'NameError: nombre 'i' no está definido' (Python 2.6.2) –

+0

@ManojGovindan: Obtuve exactamente lo que obtuve (Python 2.6.5) – inspectorG4dget

+0

Relacionado: [nombres de reenvío de la lista de Python incluso después del alcance de la comprensión . ¿Es esto correcto?] (Http://stackoverflow.com/q/4198906) –

Respuesta

33

es necesario utilizar algunos paréntesis:

((x for x in range(10) if x%2==i) for i in range(2)) 

This didn't make sense to me, so I thought it best to try something simpler first. So I went back to lists and tried:

[>>> [x for x in range(10) if x%2==i for i in range(2)] [1, 1, 3, 3, 5, 5, 7, 7, 9, 9]

Eso funcionó porque una lista previa de comprensión se filtra la variable i en el ámbito de inclusión, y convertirse en la i de la actual. Intente iniciar un nuevo intérprete de Python, y eso fallaría debido a NameError. El comportamiento de fuga del contador se ha eliminado en Python 3.

EDIT:

El equivalente de bucle para:

(x for x in range(10) if x%2==i for i in range(2)) 

sería:

l = [] 
for x in range(10): 
    if x%2 == i: 
     for i in range(2): 
      l.append(x) 

que también da un error de nombre.

Edit2:

la versión parenthesed:

((x for x in range(10) if x%2==i) for i in range(2)) 

es equivalente a:

li = [] 
for i in range(2): 
    lx = [] 
    for x in range(10): 
     if x%2==i: 
      lx.append(x) 
    li.append(lx) 
+0

Gracias, entiendo el error de fuga, pero ¿por qué son necesarios los paréntesis? ¿A qué se traduce el código sin paréntesis (en términos de un bucle for)? Entiendo que los paréntesis solucionan el problema, simplemente no entiendo por qué – inspectorG4dget

+0

@ inspectorG4dget: vea mi respuesta actualizada –

+0

, esto hace las cosas mucho más claras ahora, muchas gracias. – inspectorG4dget

3

Lie tiene la respuesta a la pregunta sintáctica. Una sugerencia: no meter tanto en el cuerpo de un generador. Una función es mucho más legible.

def make_generator(modulus): 
    return (x for x in range(10) if x % 2 == modulus) 
g = (make_generator(i) for i in range(2)) 
+0

Gracias. Entiendo esto y prefiero esto. Pero estaba intentando practicar con listas de comprensión y ver hasta dónde podía empujarlas. – inspectorG4dget

6

Ampliando la respuesta de Lie Ryan un poco:

algo = (x para x en el rango (10) si x% 2 == i para i in range (2))

es equivalente a:

def _gen1(): 
    for x in range(10): 
     if x%2 == i: 
      for i in range(2): 
       yield x 
something = _gen1() 

mientras que la versión parenthesised es equivalente a:

def _gen1(): 
    def _gen2(): 
     for x in range(10): 
      if x%2 == i: 
       yield x 

    for i in range(2): 
     yield _gen2() 
something = _gen1() 

Esto realmente dió los dos generadores:

[<generator object <genexpr> at 0x02A0A968>, <generator object <genexpr> at 0x02A0A990>] 

Desafortunadamente, los generadores de TI rendimientos son algo inestables como la salida dependerá de cómo se consumen:

>>> gens = ((x for x in range(10) if x%2==i) for i in range(2)) 
>>> for g in gens: 
     print(list(g)) 

[0, 2, 4, 6, 8] 
[1, 3, 5, 7, 9] 
>>> gens = ((x for x in range(10) if x%2==i) for i in range(2)) 
>>> for g in list(gens): 
     print(list(g)) 

[1, 3, 5, 7, 9] 
[1, 3, 5, 7, 9] 

Mi consejo es escribir el generador funciona en su totalidad: creo que tratar de obtener el alcance correcto en i sin hacer eso puede ser casi imposible.

+1

¿Por qué envolver el generador en 'list()' hace que comience en 1 en lugar de 0? (Lo probé con más generadores, parece que todos los generadores están haciendo lo mismo) –

+0

Cuando se consumen, los generadores siempre hacen lo mismo: te dan números donde 'x% 2 == i'. La primera forma establece 'i' a' 0' luego produce un generador que se consume para dar los números pares, luego 'i' se convierte en 1 y se devuelve otro generador que le da números impares, N.B. era el mismo 'i' cada vez solo con diferentes valores. El uso de 'list (gens)' significa que ambos generadores se crean para que 'i' se haya establecido en' 1' antes de consumir cualquiera de los generadores. – Duncan

+0

¿Por qué el 'i' no se comparte en el primer caso? –

5

Lie de Ryan como bucle equivalente me lleva a la siguiente, que parece funcionar muy bien:

[x for i in range(2) for x in range(10) if i == x%2] 

salidas

[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] 
Cuestiones relacionadas