2009-11-20 17 views
15

In che modo Python valuta esattamente gli attributi delle classi? Mi sono imbattuto in una stranezza interessante (in Python 2.5.2) che mi piacerebbe spiegare.Valutazione degli attributi di classe e generatori

Ho una classe con alcuni attributi che sono definiti in termini di altri, attributi definiti in precedenza. Quando provo a usare un oggetto generatore, Python genera un errore, ma se uso una semplice lista ordinaria, non ci sono problemi.

Ecco l'esempio sbucciato-giù. Si noti che l'unica differenza è che Brie utilizza un'espressione di generatore, mentre Cheddar utilizza una comprensione di lista.

# Using a generator expression as the argument to list() fails 
>>> class Brie : 
...  base = 2 
...  powers = list(base**i for i in xrange(5)) 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in Brie 
    File "<stdin>", line 3, in <genexpr> 
NameError: global name 'base' is not defined 

# Using a list comprehension works 
>>> class Cheddar : 
...  base = 2 
...  powers = [base**i for i in xrange(5)] 
... 
>>> Cheddar.powers 
[1, 2, 4, 8, 16] 

# Using a list comprehension as the argument to list() works 
>>> class Edam : 
...  base = 2 
...  powers = list([base**i for i in xrange(5)]) 
... 
>>> Edam.powers 
[1, 2, 4, 8, 16] 

(Il mio caso reale era più complicato, e mi è stato la creazione di un dizionario, ma questo è l'esempio minima sono riuscito a trovare.)

mia unica ipotesi è che le list comprehension vengono calcolati in quella linea , ma le espressioni del generatore sono calcolate dopo la fine della classe, a quel punto l'ambito è cambiato. Ma non sono sicuro del motivo per cui l'espressione del generatore non agisca da chiusura e memorizzi il riferimento alla base nello scope sulla linea.

C'è una ragione per questo, e in caso affermativo, come devo pensare della meccanica di valutazione degli attributi di classe?

risposta

14

Sì, è un po 'dubbia, questo. Una classe in realtà non introduce un nuovo ambito, ma sembra un po 'come fa; costrutti come questo mostrano la differenza.

L'idea è che quando si sta utilizzando un generatore di espressione è equivalente a farlo con un lambda:

class Brie(object): 
    base= 2 
    powers= map(lambda i: base**i, xrange(5)) 

o esplicitamente come una dichiarazione funzione:

class Brie(object): 
    base= 2 

    def __generatePowers(): 
     for i in xrange(5): 
      yield base**i 

    powers= list(__generatePowers()) 

In questo caso è deselezionare che base non è nel campo di applicazione per __generatePowers; un risultato di eccezione per entrambi (a meno che tu non sia stato abbastanza sfortunato da avere anche un numero globale base, nel qual caso ottieni un errore).

Questo non accade per la comprensione delle liste a causa di alcuni dettagli interni sul modo in cui vengono valutati, tuttavia questo comportamento scompare in Python 3 che non riuscirà ugualmente per entrambi i casi. Some discussion here.

Una soluzione si può avere con un lambda con la stessa tecnica abbiamo fatto affidamento su di nuovo nei vecchi tempi prima della nested_scopes:

class Brie(object): 
    base= 2 
    powers= map(lambda i, base= base: base**i, xrange(5)) 
1

Da PEP 289:

Dopo aver esplorato molte possibilità, un consenso emerso che le questioni vincolanti erano difficili da comprendere e che gli utenti dovrebbero essere fortemente incoraggiati ad usare espressioni generatore all'interno delle funzioni che consumano immediatamente i loro argomenti . Per più complesse applicazioni, piena generatore definizioni sono sempre superiori in termini di essere ovvio sull'ambito, vita, e vincolante [6].

patch [6] (1, 2) di discussione Patch e alternative su Source Forge http://www.python.org/sf/872326

E 'come le espressioni del generatore hanno un ambito, per quanto posso capire.

Problemi correlati