2016-07-12 85 views
10

Nel codice seguente, l'assigment mc funziona bene in Python 2 e 3.Quali sono le regole di scoping di comprensione degli elenchi all'interno di una classe Python?

Il cc assegnazione, che utilizza lo stesso elenco di comprensione all'interno di una classe, lavora in Python 2, ma non riesce con Python 3.

Cosa spiega questo comportamento?

ml1 = "a b c".split() 
ml2 = "1 2 3".split() 
mc = [ i1 + i2 for i1 in ml1 for i2 in ml2 ] 

class Foo(object): 
    cl1 = ml1 
    cl2 = ml2 

    cc1 = [ i1 for i1 in cl1 ] 
    cc2 = [ i2 for i2 in cl2 ] 
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] 


print("mc = ", mc) 
foo = Foo() 
print("cc = ", foo.cc) 

ottengo questo:

(default-3.5) snafu$ python2 /tmp/z.py 
('mc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) 
('cc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) 

(default-3.5) snafu$ python3 /tmp/z.py 
Traceback (most recent call last): 
    File "/tmp/z.py", line 5, in <module> 
    class Foo(object): 
    File "/tmp/z.py", line 11, in Foo 
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] 
    File "/tmp/z.py", line 11, in <listcomp> 
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ] 
NameError: name 'cl2' is not defined 

Perché la classe variabile cl2 non definiti? Si noti che l'assegnazione di cc2 funziona correttamente, così come lo è cc1. Scambiare cl1 e cl2 nella comprensione mostra che il secondo ciclo è quello che fa scattare l'eccezione, non cl2 per sé)

Versioni:.

(default-3.5) snafu$ python2 --version 
Python 2.7.11+ 
(default-3.5) snafu$ python3 --version 
Python 3.5.1+ 
+0

Questo è correlato a variabili di classe vs variabili di istanza, non scope di comprensione. – TigerhawkT3

+0

@ TigerhawkT3: In realtà, si tratta in realtà di ambito variabile nella comprensione a livello di classe. – user2357112

+2

http://stackoverflow.com/questions/20136955/python3-nested-list-comprehension-scope –

risposta

2

In Python 3, list comprehension hanno il loro campo di applicazione, che segue le stesse regole dell'ambito di una funzione. Sai come i metodi di una classe non guardano automaticamente all'interno dell'ambito della classe per la ricerca delle variabili?

class Example: 
    var = 1 
    def this_fails(self): 
     print(var) 
Example().this_fails() # NameError 

Lo stesso dicasi per ogni portata funzione nidificata all'interno di un ambito di classe, compresa la portata della comprensione lista. La ricerca di cl2 all'interno della comprensione dell'elenco ignora l'ambito della classe e passa direttamente alle globali. Esso funziona in modo efficace in questo modo:

class Foo(object): 
    ... 
    def make_cc(outer_iterable): 
     result = [] 
     for i1 in outer_iterable: 
      for i2 in cl2: # This fails 
       result.append(i1 + i2) 
     return result 
    cc = make_cc(cl1) # cl1 is evaluated outside the comprehension scope, for reasons 

Si noti che la ricerca cl1 funziona bene, perché ciò accade in ambito di classe, al di fuori della comprensione, pur essendo sintatticamente annidato all'interno della comprensione. They made that decision indietro quando Python ha introdotto il genexps, perché cattura alcuni errori genexp più comuni in precedenza. È anche il motivo per cui funzionano le intese sugli elenchi cc1 e cc2; il loro unico uso di variabili di livello classe è nel loro esterno (solo) for iterabile.

Utilizzare le espressioni e le espressioni del generatore all'interno di una dichiarazione di classe è un casino. Non dovrebbe essere, ma lo è. Attenersi a cicli regolari o eseguire le comprensioni al di fuori dell'istruzione della classe, in modo che la semantica sia più ovvia.

+1

Questo suona ragionevole, tuttavia, non spiega (per me) perché 'cc1 = [i1 per i1 in cl1]' funziona ma 'cc = [i1 + i2 per i1 in cl1 per i2 in cl2]' no. Le regole di scoping non saranno le stesse? – mhawke

+0

@mhawke: L'iterabile del primo 'for' viene valutato al di fuori dell'ambito di comprensione e passato alla funzione anonima come argomento, molto probabilmente per la simmetria con le espressioni del generatore, che funzionano in questo modo perché rileva alcuni errori comuni in precedenza. – user2357112

+0

"L'uso di comprensioni e espressioni generatrici all'interno di un'istruzione di classe è un disastro." Sono d'accordo: le assegnazioni di successo 'cc1' e' cc2' e l'assegnazione 'cc' fallita sembrano insolitamente sorprendenti e non intuitive. – Reece