2015-03-29 13 views
27

MODIFICA: Sembra che questo sia un "bug" molto vecchio o, in realtà, una funzionalità. Vedere, ad esempio, this mailRegole di scoping della classe Python

Sto cercando di capire le regole di scoping di Python. Più precisamente, ho pensato che li capisco, ma poi ho trovato questo codice here:

x = "xtop" 
y = "ytop" 
def func(): 
    x = "xlocal" 
    y = "ylocal" 
    class C: 
     print(x) 
     print(y) 
     y = 1 
func() 

In Python 3.4 l'output è:

xlocal 
ytop 

Se si sostituisce la classe interna da una funzione allora ragionevolmente dà UnboundLocalError. Potresti spiegarmi perché si comporta in modo strano con le classi e qual è la ragione di tale scelta delle regole di scoping?

+1

Non darà alcun errore a meno che non si chiami la funzione interna –

+3

Interessante; 'y' dovrebbe essere un'eccezione non associata nel caso di' y', ma non lo è nemmeno quando rendi 'C' un globale. Custodie ad angolo FTW! I corpi di classe sono speciali; non creano un nuovo ambito personale. Di conseguenza, y = 1' lo renderebbe locale, ma fino a quel momento è un enunciato globale * in classe ** solo ***. –

+2

[9.2. Python Scopes and Namespaces] (https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces) e [4.1. Naming e binding] (https://docs.python.org/3/reference/executionmodel.html#naming-and-binding) sono un inizio, potrebbe essere necessario rileggerli spesso fino a quando non affonda. – wwii

risposta

13

TL ; DR: questo comportamento esiste già da Python 2.1 PEP 227: Nested Scopes ed era noto all'epoca. Se un nome è assegnato all'interno di un corpo di classe (come), allora si presume che sia una variabile locale/globale; se non è assegnato a (x), può anche indicare una cella di chiusura. Le variabili lessicali non vengono visualizzate come nomi locali/globali nel corpo della classe.


In Python 3.4, dis.dis(func) mostra la seguente:

>>> dis.dis(func) 
    4   0 LOAD_CONST    1 ('xlocal') 
       3 STORE_DEREF    0 (x) 

    5   6 LOAD_CONST    2 ('ylocal') 
       9 STORE_FAST    0 (y) 

    6   12 LOAD_BUILD_CLASS 
      13 LOAD_CLOSURE    0 (x) 
      16 BUILD_TUPLE    1 
      19 LOAD_CONST    3 (<code object C at 0x7f083c9bbf60, file "test.py", line 6>) 
      22 LOAD_CONST    4 ('C') 
      25 MAKE_CLOSURE    0 
      28 LOAD_CONST    4 ('C') 
      31 CALL_FUNCTION   2 (2 positional, 0 keyword pair) 
      34 STORE_FAST    1 (C) 
      37 LOAD_CONST    0 (None) 
      40 RETURN_VALUE 

I LOAD_BUILD_CLASS carichi il builtins.__build_class__ in pila; questo è chiamato con argomenti __build_class__(func, name); dove func è il corpo della classe e name è 'C'. Il corpo della classe è la costante # 3 per la funzione func:

>>> dis.dis(func.__code__.co_consts[3]) 
    6   0 LOAD_NAME    0 (__name__) 
       3 STORE_NAME    1 (__module__) 
       6 LOAD_CONST    0 ('func.<locals>.C') 
       9 STORE_NAME    2 (__qualname__) 

    7   12 LOAD_NAME    3 (print) 
      15 LOAD_CLASSDEREF   0 (x) 
      18 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      21 POP_TOP 

    8   22 LOAD_NAME    3 (print) 
      25 LOAD_NAME    4 (y) 
      28 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      31 POP_TOP 

    9   32 LOAD_CONST    1 (1) 
      35 STORE_NAME    4 (y) 
      38 LOAD_CONST    2 (None) 
      41 RETURN_VALUE 

All'interno del corpo della classe, x si accede con LOAD_CLASSDEREF (15) mentre è y carico con LOAD_NAME (25). LOAD_CLASSDEREF è un codice operativo Python 3.4+ per caricare i valori dalle celle di chiusura specificatamente all'interno dei corpi delle classi (nelle versioni precedenti veniva utilizzato il generico LOAD_DEREF); il LOAD_NAME è per il caricamento dei valori da locals e quindi globals. Tuttavia, le celle di chiusura non mostrano né la gente del posto né i globali.

Ora, poiché il nome è memorizzato all'interno del corpo della classe (35), viene sempre utilizzato come cella di chiusura ma come nome locale/globale. Le celle di chiusura non vengono visualizzate come variabili locali nel corpo della classe.

Questo comportamento è stato vero ever since implementing PEP 227 - nested scopes. E a quel tempo BDFL ha dichiarato che questo non dovrebbe essere risolto - e quindi è stato per questi 13+ anni.


L'unica variazione rispetto PEP 227 è l'aggiunta di nonlocal in Python 3; se si usa all'interno del corpo della classe, il corpo classe può impostare i valori delle celle nell'ambito contenente:

x = "xtop" 
y = "ytop" 
def func(): 
    x = "xlocal" 
    y = "ylocal" 
    class C: 
     nonlocal y # y here now refers to the outer variable 
     print(x) 
     print(y) 
     y = 1 

    print(y) 
    print(C.y) 

func() 

L'uscita ora è

xlocal 
ylocal 
1 
Traceback (most recent call last): 
    File "test.py", line 15, in <module> 
    func() 
    File "test.py", line 13, in func 
    print(C.y) 
AttributeError: type object 'C' has no attribute 'y' 

Cioè, print(y) leggere il valore di la cella dell'ambito di contenimento e y = 1 imposta il valore in quella cella; in questo caso, non è stato creato alcun attributo per la classe C.

+0

Sembra che abbia capito la tua spiegazione. Poiché 'y' è memorizzato," ignora "il solito meccanismo. Ma non sembra un bug per te? – ivanl

+4

Se Guido dice che non è un bug, allora è una funzionalità. ;) –

+1

@ivanl: è una nota "caratteristica" dell'implementazione; vedi l'email con i link Antti. Gli ambiti delle classi non sono come gli ambiti delle funzioni. –

7

Prima concentrarsi sul caso di una chiusura - una funzione all'interno di una funzione:

x = "xtop" 
y = "ytop" 
def func(): 
    x = "xlocal" 
    y = "ylocal" 
    def inner(): 
#  global y 
     print(x) 
     print(y) 
     y='inner y' 
     print(y) 
    inner() 

Annotare il commentata global in inner Se si esegue questo, si replica il UnboundLocalError che hai. Perché?

dis.dis eseguire su di esso:

>>> import dis 
>>> dis.dis(func) 
    6   0 LOAD_CONST    1 ('xlocal') 
       3 STORE_DEREF    0 (x) 

    7   6 LOAD_CONST    2 ('ylocal') 
       9 STORE_FAST    0 (y) 

    8   12 LOAD_CLOSURE    0 (x) 
      15 BUILD_TUPLE    1 
      18 LOAD_CONST    3 (<code object inner at 0x101500270, file "Untitled 3.py", line 8>) 
      21 LOAD_CONST    4 ('func.<locals>.inner') 
      24 MAKE_CLOSURE    0 
      27 STORE_FAST    1 (inner) 

14   30 LOAD_FAST    1 (inner) 
      33 CALL_FUNCTION   0 (0 positional, 0 keyword pair) 
      36 POP_TOP 
      37 LOAD_CONST    0 (None) 
      40 RETURN_VALUE 

nota la modalità di accesso diverso di x vs y all'interno di func. L'uso di y='inner y' all'interno di inner ha creato il UnboundLocalError

Ora rimuovere il commento global y all'interno di inner. Ora che avete senza ambiguità creare y per essere la versione top globale fino a quando le dimissioni da y='inner y'

Con global non commentate, stampe:

xlocal 
ytop 
inner y 

È può ottenere un risultato più ragionevole con:

x = "xtop" 
y = "ytop" 
def func(): 
    global y, x 
    print(x,y) 
    x = "xlocal" 
    y = "ylocal" 
    def inner(): 
     global y 
     print(x,y) 
     y = 'inner y' 
     print(x,y) 
    inner()  

Stampe:

xtop ytop 
xlocal ylocal 
xlocal inner y 

L'analisi della classe di chiusura è complicata dalle variabili istanza vs classe e da cosa/quando una classe nuda (senza istanza) viene eseguita.

La riga di fondo è la stessa: se si fa riferimento a un nome al di fuori del namespace locale e quindi si assegna allo stesso nome localmente si ottiene un risultato sorprendente.

La 'correzione' è lo stesso: utilizzare la parola chiave globale:

x = "xtop" 
y = "ytop" 
def func(): 
    global x, y 
    x = "xlocal" 
    y = "ylocal" 
    class Inner: 
     print(x, y) 
     y = 'Inner_y' 
     print(x, y) 

Stampe:

xlocal ylocal 
xlocal Inner_y 

Si può leggere di più su Python regole 3 ambito in PEP 3104

+5

Capisco il comportamento delle funzioni che hai indicato. Ma non è ancora chiaro quale sia la ragione per cui _classes_ si comporta diversamente. – ivanl

+0

Le classi saranno simili ma è necessario qualificare ulteriormente le istanze contro le variabili di classe e le variabili di istanza nidificate di classe e nidificate. La linea di fondo è la stessa: la presenza dell'assegnazione di 'y = 1' rende il riferimento' print (y) 'al di sopra del compito ambiguo all'interno della chiusura. – dawg

+0

Downvoter: Posso chiederti se hai un suggerimento per un miglioramento? – dawg