L'ambito della classe e la comprensione dell'elenco, dell'insieme o del dizionario, nonché le espressioni del generatore non si mescolano.
Il perché; o, la parola ufficiale su questo
In Python 3, alla comprensione dell'elenco è stato assegnato un ambito proprio (spazio dei nomi locale) proprio, per impedire che le loro variabili locali sanguinassero all'interno dell'ambito circostante (vedi la comprensione dell'elenco Python che collega i nomi anche dopo l'ambito della comprensione. È giusto? ). È fantastico quando si utilizza una tale comprensione di elenco in un modulo o in una funzione, ma nelle classi lo scoping è un po ' strano .
Questo è documentato nel pep 227 :
I nomi nell'ambito della classe non sono accessibili. I nomi vengono risolti nell'ambito della funzione di chiusura più interna. Se una definizione di classe si verifica in una catena di ambiti nidificati, il processo di risoluzione salta le definizioni di classe.
e nella class
documentazione relativa all'istruzione composta :
La suite della classe viene quindi eseguita in un nuovo frame di esecuzione (vedere la sezione Denominazione e associazione ), utilizzando uno spazio dei nomi locale appena creato e lo spazio dei nomi globale originale. (Di solito, la suite contiene solo definizioni di funzioni.) Quando la suite della classe termina l'esecuzione, il suo frame di esecuzione viene scartato ma il suo spazio dei nomi locale viene salvato . [4] Un oggetto di classe viene quindi creato utilizzando l'elenco di ereditarietà per le classi di base e lo spazio dei nomi locale salvato per il dizionario degli attributi.
Enfasi mia; il frame di esecuzione è l'ambito temporaneo.
Poiché l'ambito viene riproposto come gli attributi di un oggetto di classe, consentendone l'utilizzo come ambito non locale porta anche a un comportamento indefinito; cosa accadrebbe se un metodo di classe indicato x
come una variabile di ambito nidificata, quindi manipolasse Foo.x
anche, per esempio? Ancora più importante, cosa significherebbe per le sottoclassi di Foo
? Python deve trattare un ambito di classe in modo diverso poiché è molto diverso da un ambito di funzione.
Infine, ma sicuramente non meno importante, la sezione Denominazione e associazione collegati nella documentazione del modello di esecuzione menziona esplicitamente gli ambiti di classe:
L'ambito dei nomi definiti in un blocco di classe è limitato al blocco di classe; non si estende ai blocchi di codice dei metodi - questo include comprensioni ed espressioni del generatore poiché sono implementate usando un ambito di funzione. Ciò significa che non riusciranno:
class A:
a = 42
b = list(a + i for i in range(10))
Quindi, per riassumere: non è possibile accedere all'ambito della classe da funzioni, comprensioni di elenchi o espressioni di generatori racchiuse in tale ambito; agiscono come se tale ambito non esistesse. In Python 2, le comprensioni degli elenchi sono state implementate usando una scorciatoia, ma in Python 3 hanno il loro ambito di funzioni (come avrebbero dovuto avere da sempre) e quindi il tuo esempio si interrompe. Altri tipi di comprensione hanno il loro ambito indipendentemente dalla versione di Python, quindi un esempio simile con una comprensione set o dict si spezzerebbe in Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
La (piccola) eccezione; o, perché una parte potrebbe ancora funzionare
C'è una parte di un'espressione di comprensione o generatore che viene eseguita nell'ambito circostante, indipendentemente dalla versione di Python. Sarebbe l'espressione per l'iterabile più esterno. Nel tuo esempio, è il range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Pertanto, l'utilizzo x
in quell'espressione non genererebbe un errore:
# Runs fine
y = [i for i in range(x)]
Questo vale solo per l'iterabile più esterno; se una comprensione ha più for
clausole, gli iterabili per le for
clausole interne vengono valutati nell'ambito della comprensione:
# NameError
y = [i for i in range(1) for j in range(x)]
Questa decisione di progettazione è stata presa al fine di generare un errore al momento della creazione di genexp invece del tempo di iterazione quando la creazione dell'iterabile più esterno di un'espressione del generatore genera un errore o quando l'iterabile più esterno risulta non essere iterabile. Le comprensioni condividono questo comportamento per coerenza.
Guardando sotto il cofano; o, molto più dettagli di quanto tu abbia mai desiderato
Puoi vedere tutto in azione usando il dis
modulo . Sto usando Python 3.3 nei seguenti esempi, perché aggiunge nomi qualificati che identificano ordinatamente gli oggetti di codice che vogliamo ispezionare. Il bytecode prodotto è altrimenti identico dal punto di vista funzionale a Python 3.2.
Per creare una classe, Python essenzialmente prende l'intera suite che compone il corpo della classe (quindi tutto rientrava di un livello più in profondità della class <name>:
linea), e lo esegue come se fosse una funzione:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Il primo LOAD_CONST
carica un oggetto codice per il Foo
corpo della classe, quindi lo trasforma in una funzione e lo chiama. Il risultato di quella chiamata viene quindi utilizzato per creare lo spazio dei nomi della classe, il suo __dict__
. Fin qui tutto bene.
La cosa da notare qui è che il bytecode contiene un oggetto codice nidificato; in Python, le definizioni di classe, le funzioni, le comprensioni e i generatori sono tutti rappresentati come oggetti di codice che contengono non solo il bytecode, ma anche strutture che rappresentano variabili locali, costanti, variabili prese dai globi e variabili prese dall'ambito nidificato. Il bytecode compilato si riferisce a quelle strutture e l'interprete python sa come accedere a quelle date i bytecode presentati.
La cosa importante da ricordare qui è che Python crea queste strutture in fase di compilazione; la class
suite è un oggetto codice ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) che è già stato compilato.
Ispezioniamo quell'oggetto di codice che crea il corpo della classe stesso; gli oggetti di codice hanno una co_consts
struttura:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Il bytecode sopra crea il corpo della classe. La funzione viene eseguita e lo locals()
spazio dei nomi risultante , contenente x
e y
viene utilizzato per creare la classe (tranne per il fatto che non funziona perché x
non è definito come globale). Si noti che dopo la memorizzazione 5
in x
, si carica un altro codice oggetto; questa è la comprensione della lista; è racchiuso in un oggetto funzione proprio come il corpo della classe; la funzione creata prende un argomento posizionale, l' range(1)
iterabile da usare per il suo codice di ciclo, trasmesso a un iteratore. Come mostrato nel bytecode, range(1)
viene valutato nell'ambito della classe.
Da ciò si può vedere che l'unica differenza tra un oggetto di codice per una funzione o un generatore e un oggetto di codice per una comprensione è che quest'ultimo viene eseguito immediatamente quando viene eseguito l'oggetto di codice principale; il bytecode crea semplicemente una funzione al volo e la esegue in pochi piccoli passaggi.
Python 2.x usa invece il bytecode in linea lì, qui è l'output di Python 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Nessun oggetto di codice viene caricato, invece un FOR_ITER
ciclo viene eseguito in linea. Quindi in Python 3.x, al generatore di elenchi è stato assegnato un oggetto codice proprio, il che significa che ha un suo ambito.
Tuttavia, la comprensione è stata compilata insieme al resto del codice sorgente di Python quando il modulo o lo script sono stati caricati per la prima volta dall'interprete e il compilatore non considera una suite di classi un ambito valido. Qualsiasi variabile referenziata nella comprensione di un elenco deve guardare in modo ricorsivo nell'ambito che circonda la definizione della classe. Se la variabile non è stata trovata dal compilatore, la contrassegna come globale. Il disassemblaggio dell'oggetto codice di comprensione elenco mostra che x
è effettivamente caricato come globale:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Questo blocco di bytecode carica il primo argomento passato (l' range(1)
iteratore), e proprio come la versione Python 2.x usa FOR_ITER
per eseguire il loop su di esso e creare il suo output.
Se avessimo definito x
nella foo
funzione invece, x
sarebbe stata una variabile di cella (le celle si riferiscono a ambiti nidificati):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Il LOAD_DEREF
sarà indirettamente caricare x
dagli oggetti cellulari codice oggetto:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Il riferimento effettivo cerca il valore dalle strutture di dati del frame corrente, che sono state inizializzate dall'attributo di un oggetto funzione .__closure__
. Poiché la funzione creata per l'oggetto codice di comprensione viene nuovamente scartata, non possiamo controllare la chiusura di quella funzione. Per vedere una chiusura in azione, dovremmo invece ispezionare una funzione nidificata:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Quindi, per riassumere:
- Le comprensioni di elenchi ottengono i propri oggetti di codice in Python 3 e non vi è alcuna differenza tra oggetti di codice per funzioni, generatori o comprensioni; gli oggetti del codice di comprensione sono racchiusi in un oggetto funzione temporaneo e chiamati immediatamente.
- Gli oggetti codice vengono creati in fase di compilazione e tutte le variabili non locali vengono contrassegnate come variabili globali o libere, in base agli ambiti nidificati del codice. Il corpo della classe non è considerato un ambito per la ricerca di tali variabili.
- Quando si esegue il codice, Python deve solo guardare nei globi o la chiusura dell'oggetto attualmente in esecuzione. Poiché il compilatore non includeva il corpo della classe come ambito, lo spazio dei nomi delle funzioni temporanee non viene considerato.
Una soluzione alternativa; o, cosa fare al riguardo
Se si dovesse creare un ambito esplicito per la x
variabile, come in una funzione, è possibile utilizzare variabili con ambito di classe per una comprensione dell'elenco:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
La funzione 'temporanea' y
può essere chiamata direttamente; lo sostituiamo quando lo facciamo con il suo valore di ritorno. Il suo scopo viene preso in considerazione quando si risolve x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Ovviamente, le persone che leggono il tuo codice si grattano un po 'la testa; potresti voler inserire un grande commento grasso che spiega perché lo stai facendo.
La soluzione migliore è usare semplicemente __init__
per creare una variabile di istanza invece:
def __init__(self):
self.y = [self.x for i in range(1)]
ed evita tutti i graffi alla testa e le domande per spiegarti. Per il tuo esempio concreto, non memorizzerei nemmeno namedtuple
la lezione; utilizzare direttamente l'output (non memorizzare affatto la classe generata) o utilizzare un valore globale:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
su Python 3.2 e 3.3 che è quello che mi aspetterei.