Come accedere alla classe esterna da una classe interna?


102

Ho una situazione del genere ...

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

Come posso accedere al Outermetodo della Innerclasse dalla classe?


Perché stai facendo questo? Cosa c'è di sbagliato nelle semplici relazioni tra pari? Stai cercando di "nascondere" qualcosa?
S.Lott

1
Un esempio di questo scenario potrebbe essere avere una classe con sottoclassi che devono accedere alla classe esterna, come di consueto, ma che poi devono creare un'altra classe (livello superiore) derivata dalla prima classe. In tal caso, le sottoclassi della seconda classe tenterebbero di accedere al genitore utilizzando self.<original_parent_name>e ottenere la classe originale, non la nuova classe da cui sono una sottoclasse . Spero che le persone che leggono questo possano visualizzare questo difficile scenario e vedere il punto di domande come questa.
Edward

1
Duplica in stackoverflow.com/questions/2278426 e ha una bella risposta.
xmedeko

Risposte:


68

I metodi di una classe nidificata non possono accedere direttamente agli attributi di istanza della classe esterna.

Nota che non è necessariamente il caso che esista un'istanza della classe esterna anche quando hai creato un'istanza della classe interna.

Infatti, è spesso sconsigliato l'uso di classi annidate, poiché l'annidamento non implica alcuna relazione particolare tra le classi interne ed esterne.


1
Hmm, Python è più vivace di Java / C ++ ... vedi la mia risposta di seguito. Se stiamo dividendo i capelli, cosa che di solito facciamo, non potrei davvero dirti se la mia "classe annidata nel metodo" conta come una classe interna. A questo punto, però, devo invocare la digitazione a papera: se fa tutto ciò che una classe interna potrebbe fare ... da un punto di vista pitonico è probabilmente il momento di annoiarsi con i peli che si dividono
mike rodent

21
Una classe interna ovviamente implica una relazione con la classe esterna, che ha tipicamente a che fare con l'ambito di utilizzo implicito della classe interna o altrimenti uno spazio dei nomi dell'organizzazione.
Acumenus

67

Stai tentando di accedere all'istanza della classe di Outer, dall'istanza della classe interna. Quindi usa il metodo factory per costruire l'istanza Inner e passarle l'istanza Outer.

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

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

        def inner_method(self):
            self.outer_instance.anothermethod()

Questa è esattamente la risposta corretta - dovrebbe essere scelta come risposta corretta poiché fornisce una soluzione alla domanda dell'OP - grazie mille!
Daniel

29

forse sono pazzo ma questo sembra davvero molto facile - il fatto è rendere la tua classe interiore all'interno di un metodo della classe esterna ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Inoltre ... "self" è usato solo per convenzione, quindi puoi farlo:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Si potrebbe obiettare che non è possibile creare questa classe interna dall'esterno della classe esterna ... ma questo non è vero:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

poi, da qualche parte a miglia di distanza:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

anche spingere la barca un po 'fuori ed estendere questa classe interna (NB per far funzionare super () devi cambiare la firma della classe di mooble in "class mooble (object)"

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

dopo

mrh1997 ha sollevato un punto interessante sull'ereditarietà non comune delle classi interne fornite utilizzando questa tecnica. Ma sembra che la soluzione sia piuttosto semplice:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))

1
Questo funziona per me. Come si chiama esattamente questo costrutto? Una funzione di fabbrica? Una chiusura?
nakedfanatic

Non ho la più pallida idea di come si chiami ... ma potrei suggerire che il motivo per cui gli altri poster non l'hanno visto è perché forse non è stato pienamente compreso che la maggior parte delle cose in Python non sono sacre, incluso "self "(nome arbitrario) e classi - sono" oggetti di prima classe ", il che sembra significare che puoi manipolarli in modi piuttosto oltraggiosi
mike rodent,

Ottimo lavoro. Ispirato da questa idea di soluzione e da un po 'di riflessione, ho ampliato parti di questa risposta per creare un'altra risposta di seguito con qualche spiegazione in più.
Edward

1
@mikerodent Perché al tuo commento (sulla mia risposta), ora ho modificato la mia risposta per rimuovere la descrizione "wiki della comunità". Come te, non ho alcuna conoscenza delle risposte "community wiki", quindi è bene che tu abbia corretto il tuo errore.
Edward

@mikerodent Inoltre, senza offesa, ma non credo che le risposte "community wiki" abbiano un tag "community wiki", devono essere solo un tipo di risposta. Probabilmente ci sarà una pagina di aiuto con una descrizione delle risposte "community wiki" su Stack Overflow se ti è capitato di essere interessato.
Edward

4

Intendi usare l'ereditarietà invece di nidificare classi come questa? Quello che stai facendo non ha molto senso in Python.

Puoi accedere a Outersome_method di s semplicemente facendo riferimento Outer.some_methodai metodi della classe interna, ma non funzionerà come ti aspetti. Ad esempio, se provi questo:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

... riceverai un'eccezione TypeError quando inizializzi un Inneroggetto, perché si Outer.some_methodaspetta di ricevere Outerun'istanza come primo argomento. (Nell'esempio sopra, stai fondamentalmente cercando di chiamare some_methodcome metodo di classe di Outer.)


1
Il motivo per cui probabilmente non ha senso è perché sono intenzionalmente hacky. L'aggiunta di metodi personalizzati a un QuerySet in Django richiede un po 'di codice boilerplate e stavo tentando di derivare un modo intelligente per farlo usando Python che mi ha permesso di modellare il codice boilerplate e semplicemente scrivere le parti pertinenti nel mio codice Model.
T. Stone

Mi scuso - Non conosco Django e quindi non posso suggerire un modo per modellare il codice boilerplate, ma potresti abbaiare all'albero sbagliato nel tentativo di annidare le tue classi. La tua classe Interiore non acquisisce nulla dalla tua classe Esterna. Tutto ciò che si annida in Outer fa è costringerti ad accedervi tramite Outer.Inner, piuttosto che semplicemente Inner.
zenbot

3

Puoi accedere facilmente alla classe esterna usando la metaclasse: dopo la creazione della classe esterna controlla il suo attributo dict per qualsiasi classe (o applica la logica di cui hai bisogno - il mio è solo un esempio banale) e imposta i valori corrispondenti:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

Utilizzando questo approccio, puoi facilmente associare e fare riferimento a due classi tra loro.


3

Ho creato del codice Python per utilizzare una classe esterna dalla sua classe interna , basato su una buona idea di un'altra risposta a questa domanda. Penso che sia breve, semplice e facile da capire.

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

Il codice principale, "pronto per la produzione" (senza commenti, ecc.). Ricordarsi di sostituire tutti i valori tra parentesi angolari (ad esempio <x>) con il valore desiderato.

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

Spiegazione di come funziona questo metodo (i passaggi di base):

  1. Creare una funzione denominata _subclass_containerper fungere da wrapper per accedere alla variabile self, un riferimento alla classe di livello superiore (dal codice in esecuzione all'interno della funzione).

    1. Creare una variabile denominata _parent_classche sia un riferimento alla variabile selfdi questa funzione, a cui le sottoclassi di _subclass_containerpossono accedere (evita conflitti di nome con altre selfvariabili nelle sottoclassi).

    2. Restituisce la sottoclasse / sottoclassi come un dizionario / elenco in modo che il codice che chiama la _subclass_containerfunzione possa accedere alle sottoclassi all'interno.

  2. Nella __init__funzione all'interno della classe di livello superiore (o ovunque sia necessario), ricevi le sottoclassi restituite dalla funzione _subclass_containernella variabile subclasses.

  3. Assegna le sottoclassi memorizzate nella subclassesvariabile agli attributi della classe di livello superiore.

Alcuni suggerimenti per semplificare gli scenari:

Rendere il codice per assegnare le sottoclassi alla classe di livello superiore più facile da copiare e da utilizzare nelle classi derivate dalla classe di livello superiore che hanno la loro __init__ funzione modificata:

Inserire prima della riga 12 nel codice principale:

def _subclass_init(self):

Quindi inserire in questa funzione le righe 5-6 (del codice principale) e sostituire le righe 4-7 con il seguente codice:

self._subclass_init(self)

Rendere possibile l'assegnazione di sottoclassi alla classe di livello superiore quando ci sono molte / quantità sconosciute di sottoclassi.

Sostituisci la riga 6 con il codice seguente:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

Scenario di esempio in cui questa soluzione sarebbe utile e in cui il nome della classe di livello superiore dovrebbe essere impossibile da ottenere:

Viene class a:creata una classe denominata "a" ( ). Ha sottoclassi che devono accedervi (il genitore). Una sottoclasse è chiamata "x1". In questa sottoclasse, il codice a.run_func()viene eseguito.

Quindi viene creata un'altra classe, denominata "b", derivata dalla classe "a" ( class b(a):). Dopodiché, viene eseguito del codice b.x1()(chiamando la funzione secondaria "x1" di b, una sottoclasse derivata). Questa funzione viene eseguita a.run_func()chiamando la funzione "run_func" della classe "a", non la funzione "run_func" del suo genitore, "b" (come dovrebbe), perché la funzione definita nella classe "a" è impostata per fare riferimento alla funzione della classe "a", poiché quella era il suo genitore.

Ciò causerebbe problemi (es. Se la funzione a.run_funcè stata cancellata) e l'unica soluzione senza riscrivere il codice in classe a.x1sarebbe ridefinire la sottoclasse x1con codice aggiornato per tutte le classi derivate dalla classe "a" che ovviamente sarebbe difficile e non vale la pena esso.


grazie per il tuo bel commento alla mia risposta. Non sapevo nulla dei tag "risposta wiki della comunità" e ora l'ho rimosso! Quindi grazie anche per averlo fatto notare. Potresti voler modificare la tua risposta di conseguenza, per evitare confusione ai futuri lettori!
mike rodent

3

Ho trovato questo .

Ottimizzato per soddisfare la tua domanda:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

Sono sicuro che in qualche modo puoi scrivere un decoratore per questo o qualcosa del genere

related: Qual è lo scopo delle classi interne di Python?


2

Un'altra possibilità:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

1

Espandendo il pensiero convincente di @ tsnorri, che il metodo esterno potrebbe essere un metodo statico :

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

Ora la linea in questione dovrebbe funzionare nel momento in cui viene effettivamente chiamata.

L'ultima riga nel codice precedente fornisce alla classe Inner un metodo statico che è un clone del metodo statico Outer.


Questo sfrutta due funzionalità di Python, che le funzioni sono oggetti e l' ambito è testuale .

Di solito, l'ambito locale fa riferimento ai nomi locali della funzione corrente (testualmente).

... o la classe attuale nel nostro caso. Quindi gli oggetti "locali" alla definizione della classe Outer ( Innere some_static_method) possono essere indicati direttamente all'interno di quella definizione.


1

Qualche anno in ritardo per la festa ... ma per espandere @mike rodentla meravigliosa risposta, ho fornito il mio esempio di seguito che mostra quanto sia flessibile la sua soluzione e perché dovrebbe essere (o avrebbe dovuto essere) accettata risposta.

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

Produzione:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

Ancora una volta, una risposta meravigliosa, oggetti di scena per te Mike!


-2

È troppo semplice:

Ingresso:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

Produzione

classe A func1

classe B func1


2
Hmm, nota la domanda e le parole accedono alla classe esterna . Per una felice, piuttosto divertente, coincidenza, le vostre classi esterne e interne hanno entrambe un metodo func1. E, altrettanto divertente, crei un Ainterno del costruttore di B, che NON è assolutamente l' Aoggetto della classe esterna di quel particolare B.
mike rodent
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.