Jak uzyskać dostęp do klasy zewnętrznej z klasy wewnętrznej?


102

Mam taką sytuację ...

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

Jak mogę uzyskać dostęp do Outermetody Innerklasy z poziomu klasy?


Dlaczego to robisz? Co jest złego w prostych relacjach z rówieśnikami? Czy próbujesz coś „ukryć”?
S.Lott

1
Przykładem tego scenariusza może być posiadanie klasy z podklasami, które muszą uzyskać dostęp do klasy zewnętrznej, jak zwykle, ale następnie trzeba utworzyć inną klasę (najwyższy poziom) pochodzącą z pierwszej klasy. W takim przypadku, podklasy klasy drugiej byłby próby uzyskania dostępu do rodzica użyciu self.<original_parent_name>i dostać oryginalny klasę, a nie nową klasę, że są one z podklasy . Mam nadzieję, że ludzie, którzy to czytają, mogą sobie wyobrazić ten trudny scenariusz i dostrzec sens takich pytań.
Edward

1
Powiel na stackoverflow.com/questions/2278426 i ma fajną odpowiedź.
xmedeko

Odpowiedzi:


68

Metody klasy zagnieżdżonej nie mogą uzyskać bezpośredniego dostępu do atrybutów instancji klasy zewnętrznej.

Należy zauważyć, że niekoniecznie jest tak, że instancja klasy zewnętrznej istnieje, nawet jeśli utworzono instancję klasy wewnętrznej.

W rzeczywistości często odradza się używanie klas zagnieżdżonych, ponieważ zagnieżdżenie nie implikuje żadnego szczególnego związku między klasami wewnętrzną i zewnętrzną.


1
Hmm, Python jest bardziej zadziorny niż Java / C ++ ... Zobacz moją odpowiedź poniżej. Jeśli dzielimy włosy, co zwykle bywa, nie mogłem powiedzieć, czy moja „klasa zagnieżdżona w metodzie” liczy się jako klasa wewnętrzna. W tym momencie muszę jednak odwołać się do pisania kaczego: jeśli robi wszystko, co może zrobić klasa wewnętrzna ... z Pythonowego punktu widzenia prawdopodobnie czas znudzić się rozłupywaniem włosów
mike gryzoń

21
Klasa wewnętrzna oczywiście implikuje relację z klasą zewnętrzną, zazwyczaj mając do czynienia z domniemanym zakresem użycia klasy wewnętrznej lub w inny sposób organizacyjną przestrzenią nazw.
Acumenus,

67

Próbujesz uzyskać dostęp do instancji klasy Outer z instancji klasy wewnętrznej. Po prostu użyj metody fabrycznej, aby zbudować instancję Inner i przekazać do niej instancję 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()

To jest dokładnie poprawna odpowiedź - powinna zostać wybrana jako poprawna odpowiedź, ponieważ zapewnia odpowiedź na pytanie PO - bardzo dziękuję!
Daniel

29

może jestem szalony, ale wydaje się to rzeczywiście bardzo łatwe - chodzi o to, aby uczynić swoją wewnętrzną klasę metodą klasy zewnętrznej ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

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

Poza tym ... „siebie” jest używane tylko w konwencji, więc możesz to zrobić:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

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

Można by się sprzeciwić, że nie możesz wtedy stworzyć tej klasy wewnętrznej spoza klasy zewnętrznej ... ale to nieprawda:

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

wtedy gdzieś mile stąd:

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

nawet wypchnij trochę łódź i rozszerz tę klasę wewnętrzną (uwaga: aby super () działało, musisz zmienić sygnaturę klasy mooble na "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()

później

mrh1997 podniósł interesującą kwestię dotyczącą rzadkiego dziedziczenia klas wewnętrznych dostarczanych przy użyciu tej techniki. Ale wydaje się, że rozwiązanie jest dość proste:

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
To działa dla mnie. Jak dokładnie nazywa się ta konstrukcja? Funkcja fabryczna? Zamknięcie?
nakedfanatic,

Nie mam pojęcia, jak to się nazywa ... ale czy mogę zasugerować, że powodem, dla którego inne plakaty tego nie widziały, jest to, że być może nie w pełni doceniano, że większość rzeczy w Pythonie nie jest świętych, w tym „siebie „(dowolna nazwa) i klasy - są to„ obiekty pierwszej klasy ”, co wydaje się oznaczać, że można nimi manipulować w dość oburzający sposób
mike gryzoń

Świetna robota. Zainspirowany tym pomysłem na rozwiązanie i odrobiną przemyślenia, rozszerzyłem część tej odpowiedzi, aby utworzyć kolejną odpowiedź poniżej z dodatkowymi wyjaśnieniami.
Edward

1
@mikerodent Ponieważ w związku z Twoim komentarzem (dotyczącym mojej odpowiedzi) zmodyfikowałem teraz swoją odpowiedź, aby usunąć opis „wiki społeczności”. Podobnie jak ty, nie mam żadnej wiedzy na temat odpowiedzi na „wiki społeczności”, więc dobrze, że naprawiłeś swój błąd.
Edward

@mikerodent Również bez obrazy, ale nie sądzę, aby odpowiedzi „wiki społeczności” miały tag „wiki społeczności”, muszą być po prostu rodzajem odpowiedzi. Prawdopodobnie pojawi się strona pomocy z opisem odpowiedzi "wiki społeczności" na Stack Overflow, jeśli byłeś zainteresowany.
Edward

4

Czy masz zamiar używać dziedziczenia zamiast zagnieżdżania klas w ten sposób? To, co robisz, nie ma sensu w Pythonie.

Możesz uzyskać dostęp do Outermetody some_method, odwołując się Outer.some_methoddo metod klasy wewnętrznej, ale nie będzie ona działać tak, jak się spodziewasz. Na przykład, jeśli spróbujesz tego:

class Outer(object):

    def some_method(self):
        # do something

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

... otrzymasz TypeError podczas inicjalizacji Innerobiektu, ponieważ Outer.some_methodoczekuje , że Outerjako pierwszy argument otrzyma instancję. (W powyższym przykładzie zasadniczo próbujesz wywołać some_methodjako metodę klasy Outer.)


1
Powodem, dla którego prawdopodobnie nie ma to sensu, jest to, że celowo jestem hackerem. Dodanie niestandardowych metod do QuerySet w Django wymaga trochę standardowego kodu, a ja próbowałem znaleźć sprytny sposób, aby to zrobić za pomocą Pythona, który pozwolił mi utworzyć szablon kodu standardowego i po prostu napisać odpowiednie części w kodzie mojego modelu.
T. Stone

Przepraszam - nie znam Django, więc nie mogę zasugerować sposobu na utworzenie szablonu standardowego kodu, ale możesz szczekać niewłaściwe drzewo, próbując zagnieździć swoje klasy. Twoja klasa wewnętrzna nie nabywa niczego z klasy zewnętrznej. Wszystko, co robi zagnieżdżenie go w Outer, zmusza cię do dostępu do niego przez Outer.Inner, a nie tylko zwykły Inner.
zenbot

3

Możesz łatwo uzyskać dostęp do zewnętrznej klasy za pomocą metaklasy: po utworzeniu klasy zewnętrznej sprawdź jej atrybut dict dla dowolnej klasy (lub zastosuj dowolną logikę, której potrzebujesz - mój to tylko trywialny przykład) i ustaw odpowiednie wartości:

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!

Korzystając z tego podejścia, możesz łatwo wiązać i odnosić się do dwóch klas między sobą.


3

Stworzyłem kod w Pythonie, aby używać zewnętrznej klasy z jej klasy wewnętrznej , w oparciu o dobry pomysł z innej odpowiedzi na to pytanie. Myślę, że jest krótki, prosty i łatwy do zrozumienia.

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}

Główny kod „do produkcji” (bez komentarzy itp.). Pamiętaj, aby zastąpić wszystkie wartości w nawiasach ostrych (np. <x>) Żądaną wartością.

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>}

Wyjaśnienie, jak działa ta metoda (podstawowe kroki):

  1. Utwórz funkcję o nazwie, _subclass_containerktóra będzie działać jako opakowanie, aby uzyskać dostęp do zmiennej self, odwołanie do klasy wyższego poziomu (z kodu działającego wewnątrz funkcji).

    1. Utwórz zmienną o nazwie, _parent_classktóra jest odniesieniem do zmiennej selftej funkcji, do której podklasy _subclass_containermają dostęp (pozwala to uniknąć konfliktów nazw z innymi selfzmiennymi w podklasach).

    2. Zwróć podklasy / podklasy jako słownik / listę, aby kod wywołujący _subclass_containerfunkcję mógł uzyskać dostęp do podklas wewnątrz.

  2. W __init__funkcji wewnątrz klasy wyższego poziomu (lub gdziekolwiek jest to potrzebne), odbierz zwrócone podklasy z funkcji _subclass_containerdo zmiennej subclasses.

  3. Przypisz podklasy przechowywane w subclasseszmiennej do atrybutów klasy wyższego poziomu.

Kilka wskazówek ułatwiających scenariusze:

Ułatwienie kopiowania kodu przypisującego podklasy do klasy wyższego poziomu i używanie go w klasach pochodnych klasy wyższego poziomu, które mają __init__ funkcja została zmieniona:

Wstaw przed linią 12 w głównym kodzie:

def _subclass_init(self):

Następnie wstaw do tej funkcji wiersze 5-6 (głównego kodu) i zamień wiersze 4-7 na następujący kod:

self._subclass_init(self)

Umożliwienie przypisywania podklas do klasy wyższego poziomu, gdy istnieje wiele / nieznanych ilości podklas.

Zastąp wiersz 6 następującym kodem:

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

Przykładowy scenariusz, w którym to rozwiązanie byłoby przydatne i gdzie nie można uzyskać nazwy klasy wyższego poziomu:

Tworzona jest klasa o nazwie „a” ( class a:). Ma podklasy, które muszą mieć do niego dostęp (rodzic). Jedna podklasa nosi nazwę „x1”. W tej podklasie kod a.run_func()jest uruchamiany.

Następnie tworzona jest inna klasa o nazwie „b”, pochodząca z klasy „a” ( class b(a):). Następnie zostaje uruchomiony jakiś kod b.x1()(wywołanie funkcji podrzędnej „x1” z b, pochodnej podklasy). Ta funkcja działa a.run_func(), wywołując funkcję „run_func” klasy „a”, a nie funkcję „run_func” jej rodzica, „b” (tak jak powinna), ponieważ funkcja, która została zdefiniowana w klasie „a” ma odwoływać się do funkcji klasy „a”, ponieważ była ona jej rodzicem.

Spowodowałoby to problemy (np. Gdyby funkcja a.run_funczostała usunięta), a jedynym rozwiązaniem bez przepisywania kodu w klasie a.x1byłoby ponowne zdefiniowanie podklasy x1ze zaktualizowanym kodem dla wszystkich klas pochodzących z klasy „a”, co oczywiście byłoby trudne i niewarte to.


dziękuję za miły komentarz do mojej odpowiedzi. Nie wiedziałem nic o tagach „odpowiedzi wiki społeczności” i teraz je usunąłem! Dziękuję również za zwrócenie uwagi. Możesz odpowiednio zmodyfikować swoją odpowiedź, aby uniknąć nieporozumień dla przyszłych czytelników!
mike gryzoń

3

Znalazłem to .

Dostosowano do twojego pytania:

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)

Jestem pewien, że możesz jakoś napisać dekoratora do tego czy czegoś

powiązane: Jaki jest cel klas wewnętrznych Pythona?


2

Inna możliwość:

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

Rozwijając przekonujące myślenie @ tsnorri, że metoda zewnętrzna może być metodą statyczną :

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

Teraz dana linia powinna działać, zanim zostanie faktycznie wywołana.

Ostatni wiersz w powyższym kodzie podaje klasie Inner statyczną metodę będącą klonem statycznej metody Outer.


Wykorzystuje to dwie funkcje Pythona: funkcje są obiektami , a zakres jest tekstowy .

Zwykle zakres lokalny odwołuje się do lokalnych nazw (tekstowo) bieżącej funkcji.

... lub aktualna klasa w naszym przypadku. Zatem obiekty „lokalne” w stosunku do definicji klasy Outer ( Inneri some_static_method) mogą być odwoływane bezpośrednio w tej definicji.


1

Kilka lat spóźniłem się na imprezę ... ale aby rozwinąć @mike rodentwspaniałą odpowiedź, poniżej podałem własny przykład, który pokazuje, jak elastyczne jest jego rozwiązanie i dlaczego powinno (lub powinno być ) zaakceptowane odpowiedź.

Python 3.7.0

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 # ....
)

Wynik:

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

Ponownie, wspaniała odpowiedź, rekwizyty dla ciebie mikrofon!


-2

To jest zbyt proste:

Wejście:

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()

Wynik

klasa A func1

klasa B func1


2
Hmm, zwróć uwagę na pytanie, a słowa odnoszą się do zewnętrznej klasy . Szczęśliwym, całkiem zabawnym zbiegiem okoliczności, wasza klasa zewnętrzna i wewnętrzna mają swoją metodę func1. I, równie zabawne, tworzysz Awewnątrz konstruktora programu B, który absolutnie NIE jest zewnętrznym Aobiektem klasy tego konkretnego B.
mike gryzoń
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.