L'ambito delle classi annidate?


116

Sto cercando di capire l'ambito nelle classi nidificate in Python. Ecco il mio codice di esempio:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

La creazione della classe non viene completata e ottengo l'errore:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Provare inner_var = Outerclass.outer_varnon funziona. Ottengo:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Sto cercando di accedere alla statica outer_varda InnerClass.

C'è un modo per fare questo?

Risposte:


105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

Non è esattamente la stessa cosa che cose simili funzionano in altre lingue e utilizza la ricerca globale invece di limitare l'accesso a outer_var. (Se modifichi quale oggetto il nomeOuter è associato , questo codice utilizzerà quell'oggetto la prossima volta che verrà eseguito.)

Se invece desideri che tutti gli Inneroggetti abbiano un riferimento a un Outerperché outer_varè davvero un attributo di istanza:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Nota che la nidificazione delle classi è piuttosto insolita in Python e non implica automaticamente alcun tipo di relazione speciale tra le classi. È meglio non fare il nido. (È comunque possibile impostare un attributo di classe su Outerper Inner, se si desidera.)


2
Potrebbe essere utile aggiungere con quale versione (s) di python funzionerà la tua risposta.
AJ.

1
Ho scritto questo con 2.6 / 2.x in mente, ma, guardandolo, non vedo nulla che non funzionerebbe allo stesso modo in 3.x.

Non capisco bene cosa intendi in questa parte, "(Se cambi l'oggetto a cui è associato il nome Outer, questo codice lo utilizzerà la prossima volta che verrà eseguito)." Puoi aiutarmi a capire per favore?
batbrat

1
@batbrat significa che il riferimento a Outerviene cercato di nuovo ogni volta che lo fai Inner.inner_var. Quindi, se rileggi il nome Outera un nuovo oggetto, Inner.inner_varinizierà a restituire quel nuovo oggetto.
Felipe

45

Penso che tu possa semplicemente fare:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Il problema che hai riscontrato è dovuto a questo:

Un blocco è un pezzo di testo del programma Python che viene eseguito come un'unità. I seguenti sono blocchi: un modulo, un corpo della funzione e una definizione di classe.
(...)
Uno scope definisce la visibilità di un nome all'interno di un blocco.
(...)
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 le espressioni del generatore poiché sono implementate utilizzando un ambito di funzione. Ciò significa che quanto segue fallirà:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Quanto sopra significa:
un corpo di funzione è un blocco di codice e un metodo è una funzione, quindi i nomi definiti al di fuori del corpo della funzione presente in una definizione di classe non si estendono al corpo della funzione.

Parafrasando questo per il tuo caso:
una definizione di classe è un blocco di codice, quindi i nomi definiti dalla definizione di classe interna presente in una definizione di classe esterna non si estendono alla definizione di classe interna.


2
Sorprendente. Il tuo esempio ha esito negativo, affermando che "il nome globale 'a' non è definito". Tuttavia, la sostituzione di una lista di comprensione [a + i for i in range(10)]lega con successo Ab alla lista attesa [42..51].
George

6
@George Nota che l'esempio con la classe A non è mio, è dal documento ufficiale di Python di cui ho fornito il link. Questo esempio fallisce e quell'errore è ciò che si vuole mostrare in questo esempio. In effetti list(a + i for i in range(10))è list((a + i for i in range(10)))questo a dire list(a_generator). Dicono che un generatore sia implementato con uno scopo simile a quello delle funzioni.
eyquem

@George Per me, ciò significa che le funzioni agiscono in modo diverso a seconda che si trovino in un modulo o in una classe. Nel primo caso, una funzione va all'esterno per trovare l'oggetto associato a un identificatore libero. Nel secondo caso, una funzione, cioè un metodo, non esce dal suo corpo. Le funzioni in un modulo e i metodi in una classe sono in realtà due tipi di oggetti. I metodi non sono solo funzioni in classe. Questa è la mia idea.
eyquem

5
@George: FWIW, né la list(...)chiamata né la comprensione funzionano in Python 3. Anche la documentazione per Py3 è leggermente diversa a seconda di ciò. Ora dice " 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 le comprensioni e le espressioni del generatore poiché sono implementati utilizzando uno scopo di funzione. " (Enfasi mia ).
martineau

Sono curioso di sapere perché anche la lista (Aa + i per i nell'intervallo (10)) non funziona, dove ho corretto a di Aa penso che A possa essere un nome globale.
gaoxinge

20

Potresti stare meglio se non usi le classi annidate. Se devi annidare, prova questo:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Oppure dichiara entrambe le classi prima di annidarle:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Dopo questo puoi, del InnerClassse necessario.)


3

Soluzione più semplice:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Richiede che tu sia esplicito, ma non richiede molto sforzo.


NameError: il nome "OuterClass" non è definito - -1
Mr_and_Mrs_D

3
Il punto è accedervi dall'ambito della classe Outer, inoltre si verificherà un errore simile se lo chiamate da un ambito statico all'interno di Outer. Ti suggerisco di eliminare questo post
Mr_and_Mrs_D

1

In Python gli oggetti mutabili vengono passati come riferimento, quindi puoi passare un riferimento della classe esterna alla classe interna.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

2
Tieni presente che qui hai un ciclo di riferimento e in alcuni scenari l'istanza di questa classe non verrà liberata. Un esempio, con cPython, se avessi definito il __del__ metodo, il garbage collector non sarà in grado di gestire il ciclo di riferimento e gli oggetti entreranno in gc.garbage. Il codice sopra, così com'è, non è problematico. Il modo per affrontarlo è usare un riferimento debole . È possibile leggere la documentazione su weakref (2.7) o weakref (3.5)
guyarad

0

Tutte le spiegazioni possono essere trovate nella documentazione di Python The Python Tutorial

Per il tuo primo errore <type 'exceptions.NameError'>: name 'outer_var' is not defined. La spiegazione è:

Non esiste una scorciatoia per fare riferimento ad attributi di dati (o altri metodi!) Dall'interno dei metodi. Trovo che questo in realtà aumenti la leggibilità dei metodi: non c'è possibilità di confondere le variabili locali e le variabili di istanza quando si sfoglia un metodo.

citato da The Python Tutorial 9.4

Per il tuo secondo errore <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Quando una definizione di classe viene lasciata normalmente (tramite la fine), viene creato un oggetto di classe.

citato da The Python Tutorial 9.3.1

Quindi, quando ci provi inner_var = Outerclass.outer_var, Quterclassnon è stato ancora creato, ecco perchéname 'OuterClass' is not defined

Una spiegazione più dettagliata ma noiosa per il tuo primo errore:

Sebbene le classi abbiano accesso agli ambiti delle funzioni che racchiudono, tuttavia, non agiscono come ambiti che racchiudono il codice annidato all'interno della classe: Python cerca le funzioni che racchiudono i nomi referenziati, ma mai le classi che racchiudono. Ovvero, una classe è un ambito locale e ha accesso per racchiudere ambiti locali, ma non funge da ambito locale di inclusione per ulteriore codice nidificato.

citato da Learning.Python (5 °) .Mark.Lutz

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.