2010-09-22 17 views
37

Stavo giocando con le list comprehensions per comprenderne meglio e ho trovato alcuni output inaspettati che non sono in grado di spiegare. Non ho trovato questa domanda prima, ma se è/è/una domanda ricorrente, mi scuso.Python: Advanced Nested List Comprehension Sintassi

Stavo essenzialmente cercando di scrivere un generatore che generasse generatori. Un semplice generatore che utilizza di lista sarebbe simile a questa:

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

Quello che stavo cercando di fare è stato scrivere un generatore che ha generato due generatori - il primo dei quali generati i numeri pari nella gamma (10) e il secondo di cui ha generato i numeri dispari nell'intervallo (10). Per questo, ho fatto:

>>> (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 

Non capisco il motivo per cui 'i' viene fatto riferimento prima dell'assegnazione

ho pensato che potrebbe aver avuto qualcosa a che fare con i in range(2), così ho fatto:

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

Questo non aveva senso per me, quindi ho pensato che fosse meglio provare qualcosa di più semplice prima. Così sono tornato a liste e provato:

>>> [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] 

che mi aspettavo di essere lo stesso:

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

Ma quando ho provato su un sospetto, questo ha funzionato:

>>> [[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 

Quindi ho pensato che potesse essere un problema con il livello di oscillazione dell'istruzione if. Così ho provato:

>>> [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] 

E ora sono completamente confuso. Qualcuno può spiegare questo comportamento. Non capisco perché la mia comprensione delle liste sia malformata, né comprendo come funzioni l'ambito delle dichiarazioni if.

Qualsiasi aiuto sarebbe notevolmente apprezzato

Grazie

PS: Mentre a prova di leggere la domanda, mi sono reso conto che questo ha un aspetto un po 'come una domanda compiti a casa - non lo è.

+0

Forse '[x per x nella gamma (10), se x% 2 == i per i in range (2)] 'lavoro? Ho ottenuto 'NameError: name 'i' non è definito' (Python 2.6.2) –

+0

@ManojGovindan: Ho ottenuto esattamente quello che hai ottenuto (Python 2.6.5) – inspectorG4dget

+0

Correlati: [Python list comprehension rebind names anche dopo scope of comprehension . È giusto?] (Http://stackoverflow.com/q/4198906) –

risposta

33

è necessario utilizzare alcune parentesi:

((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]

che ha lavorato perché l'elenco precedente di comprensione perde la variabile i al campo di applicazione che racchiude, e diventare il i per quello corrente. Prova ad avviare un nuovo interprete Python, e questo fallirebbe a causa di NameError. Il comportamento fuoriuscito dal contatore è stato rimosso in Python 3.

EDIT:

L'equivalente ciclo for per:

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

sarebbe:

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

che dà anche un errore di nome.

EDIT2:

la versione parenthesed:

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

è 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

Grazie, capisco l'errore di perdita, ma perché sono necessarie le parentesi? Che cosa traduce il codice senza parentesi (in termini di ciclo for)? Capisco che le parentesi risolvono il problema, semplicemente non capisco perché – inspectorG4dget

+0

@ inspectorG4dget: vedi la mia risposta aggiornata –

+0

questo rende le cose molto più chiare ora, grazie mille. – inspectorG4dget

3

Lie ha la risposta alla domanda sintattica. Un suggerimento: non riempire così tanto il corpo di un generatore. Una funzione è molto più leggibile.

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

Grazie. Capisco questo e preferisco questo. Ma stavo cercando di ottenere un po 'di pratica con le list comprehensions e vedere fino a che punto potrei spingerli – inspectorG4dget

6

Ampliando la risposta di Lie Ryan un po ':

qualcosa = (x per x nella gamma (10) se x% 2 == i per i in range (2))

è equivalente a:

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

mentre la versione parentesi, è 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() 

Questa realtà non cedere i due generatori:

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

Purtroppo i generatori produce sono un po 'instabile come l'uscita dipenderà da come li si consumano:

>>> 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] 

Il mio consiglio è quello di scrivere il generatore funziona completamente: penso che cercare di ottenere l'ambito corretto su i senza farlo potrebbe essere quasi impossibile.

+1

Perché il wrapping del generatore in 'list()' lo fa iniziare a 1 anziché a 0? (l'ho provato con più generatori, sembra che i generatori stiano tutti facendo la stessa cosa) –

+0

Quando vengono consumati i generatori fanno sempre la stessa cosa: ti danno i numeri dove 'x% 2 == i'. Il primo modo imposta 'i' su' 0' quindi produce un generatore che viene consumato per dare i numeri pari, quindi 'i' diventa 1 e un altro generatore restituito che ti dà numeri dispari, N.B. era lo stesso 'i' ogni volta solo con valori diversi. Usare 'lista (gens)' significa che entrambi i generatori sono creati in modo che 'i' sia stato impostato su' 1' prima di consumare un generatore. – Duncan

+0

Perché il 'i' non è condiviso nel primo caso? –

5

Lie di Ryan per-loop equivalente mi porta alla seguente, che sembra funzionare bene:

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

uscite

[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] 
Problemi correlati