Как получить доступ к внешнему классу из внутреннего класса?

у меня такая ситуация...

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

Как я могу получить доступ к методу класса Outer из класса Inner?


person T. Stone    schedule 07.01.2010    source источник
comment
Почему вы это делаете? Что не так с простыми отношениями со сверстниками? Вы пытаетесь что-то скрыть?   -  person S.Lott    schedule 08.01.2010
comment
Примером этого сценария может быть наличие класса с подклассами, которым требуется доступ к внешнему классу, как обычно, но затем необходимо создать другой класс (верхний уровень), производный от первого класса. В В этом случае подклассы второго класса попытаются получить доступ к родителю с помощью self.<original_parent_name> и получить исходный класс, а не новый класс, от которого они являются подклассом. Я надеюсь, что люди, читающие это, смогут визуализировать этот сложный сценарий и увидеть смысл таких вопросов.   -  person Edward    schedule 01.02.2016
comment
Дублируйте на stackoverflow.com/questions/2278426 и получите хороший ответ.   -  person xmedeko    schedule 15.03.2018


Ответы (13)


Методы вложенного класса не могут напрямую обращаться к атрибутам экземпляра внешнего класса.

Обратите внимание, что экземпляр внешнего класса не обязательно существует, даже если вы создали экземпляр внутреннего класса.

На самом деле, часто не рекомендуется использовать вложенные классы, поскольку вложенность не предполагает какой-либо конкретной связи между внутренним и внешним классами.

person Daniel Vassallo    schedule 07.01.2010
comment
Хм, Python более резвый, чем Java/C++... см. мой ответ ниже. Если мы будем мудрить, что мы обычно и делаем, я не могу сказать вам, считается ли мой вложенный класс внутри метода внутренним классом. На данный момент, однако, я должен вызвать утиную типизацию: если он делает все, что может сделать внутренний класс... с точки зрения Pythonic, вероятно, пора надоесть мудрить. - person mike rodent; 07.11.2011
comment
Внутренний класс, конечно же, подразумевает связь с внешним классом, обычно связанную с подразумеваемой областью использования внутреннего класса или, иначе, с организационным пространством имен. - person Acumenus; 07.04.2014
comment
Они могут, если вы установите их так, как @Kitlbast предложил в другом ответе. Таким образом, если вы измените значение переменной во внешнем классе и получите доступ к нему из внутреннего класса, вы получите обновленное значение указанной переменной. - person Vasco Cansado Carvalho; 29.11.2020

Вы пытаетесь получить доступ к экземпляру класса 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()
person Kitlbast    schedule 11.08.2011
comment
Удивительно! И если вы измените что-либо во внешнем классе, внутренний класс покажет это значение соответствующим образом! - person Vasco Cansado Carvalho; 29.11.2020

может быть, я злюсь, но это действительно кажется очень простым - дело в том, чтобы сделать ваш внутренний класс внутри метода внешнего класса...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

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

Плюс... "я" используется только по соглашению, поэтому вы можете сделать это:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

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

Могут возразить, что вы не можете создать этот внутренний класс из-за пределов внешнего класса... но это неверно:

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

затем, где-то вдали:

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

даже немного подтолкните лодку и расширьте этот внутренний класс (обратите внимание, чтобы заставить super() работать, вам нужно изменить сигнатуру класса mooble на «класс mooble (объект)»

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

позже

mrh1997 поднял интересный вопрос о необычном наследовании внутренних классов, доставляемых с использованием этой техники. Но кажется, что решение довольно простое:

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 ))
person mike rodent    schedule 22.08.2011
comment
Это работает для меня. Как точно называется эта конструкция? Заводская функция? Закрытие? - person nakedfanatic; 06.09.2011
comment
Я понятия не имею, как это называется... но могу ли я предположить, что причина, по которой другие постеры этого не видели, заключается в том, что, возможно, не было полностью оценено, что большинство вещей в Python не священны, включая self ( произвольное имя) и классы - это объекты первого класса, что, по-видимому, означает, что вы можете манипулировать ими совершенно возмутительными способами. - person mike rodent; 29.09.2011
comment
Отличная работа. Вдохновленный этой идеей решения и немного подумав, я расширил части этого ответа, чтобы создать еще один ответ ниже с некоторыми дополнительными пояснениями. - person Edward; 31.01.2016
comment
@mikerodent Поскольку к вашему комментарию (к моему ответу) я отредактировал свой ответ, удалив описание вики сообщества. Как и вы, я ничего не знаю об ответах сообщества вики, поэтому хорошо, что вы исправили свою ошибку. - person Edward; 01.02.2016
comment
@mikerodent Кроме того, без обид, но я не думаю, что вики-ответы сообщества имеют вики-тег сообщества, они должны быть просто типом ответа. Вероятно, будет страница справки с описанием вики-ответов сообщества на Stack Overflow, если вам это интересно. - person Edward; 01.02.2016
comment
@ Эдвард, да, я узнал о них - они означают, что вы не получаете очков репутации, когда кто-то голосует за ваш ответ, что немного глупо. Подробности доступны на Meta Stack Exchange. - person mike rodent; 02.02.2016
comment
Вики сообщества @mikerodent — это вопросы, которые вы задаете и одновременно отвечаете на них — базы знаний. - person TheSola10; 03.03.2016
comment
Имейте в виду, что это решение создает объект нового типа для внутреннего класса каждый раз, когда вы вызываете Bumblebee().giveMeAnInnerClass(). Это особенно проблема при проверке наследования: ` class A(Bumblebee().giveMeAnInnerClass()): pass issubclass(A, Bumblebee().giveMeAnInnerClass() =› False - person mrh1997; 25.05.2017
comment
@ mrh1997 спасибо, что указали на это. Но, по моему мнению, вы прежде всего обратили внимание на желательность честных имен ваших методов, и я думаю, что в случае с giveMeAnInnerClass: использование переходного глагола и неопределенного артикля намекает пользователю класса на то, что происходит. на и каковы ограничения. - person mike rodent; 11.11.2017

Я нашел это.

Изменено, чтобы удовлетворить ваш вопрос:

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)

Я уверен, что вы можете как-то написать декоратор для этого или что-то в этом роде

связанные: Какова цель внутренних классов python? < /а>

person flying sheep    schedule 16.05.2011

Вы можете легко получить доступ к внешнему классу с помощью метакласса: после создания внешнего класса проверьте его атрибут dict для любых классов (или примените любую логику, которая вам нужна - мой пример просто тривиальный) и установите соответствующие значения:

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!

Используя этот подход, вы можете легко связывать и ссылаться между двумя классами.

person grundic    schedule 06.06.2017

Я создал код Python для использования внешнего класса из его внутреннего класса, основываясь на хорошей идее другого ответ на этот вопрос. Я думаю, что это коротко, просто и понятно.

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}

Основной код, "продакшен" (без комментариев и т.д.). Не забудьте заменить все значения в угловых скобках (например, <x>) нужным значением.

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

Объяснение того, как работает этот метод (основные шаги):

  1. Создайте функцию с именем _subclass_container, которая будет выступать в качестве оболочки для доступа к переменной self, ссылке на класс более высокого уровня (из кода, выполняемого внутри функции).

    1. Создайте переменную с именем _parent_class, которая является ссылкой на переменную self этой функции, к которой могут обращаться подклассы _subclass_container (чтобы избежать конфликтов имен с другими переменными self в подклассах).

    2. Верните подкласс/подклассы в виде словаря/списка, чтобы код, вызывающий функцию _subclass_container, мог получить доступ к подклассам внутри.

  2. В функции __init__ внутри класса более высокого уровня (или где еще необходимо) получить возвращенные подклассы из функции _subclass_container в переменную subclasses.

  3. Назначьте подклассы, хранящиеся в переменной subclasses, атрибутам класса более высокого уровня.

Несколько советов, как упростить сценарии:

Упрощено копирование кода для назначения подклассов классу более высокого уровня и его использование в классах, производных от класса более высокого уровня, у которых __init__ изменена функция:

Вставьте перед строкой 12 основного кода:

def _subclass_init(self):

Затем вставьте в эту функцию строки 5-6 (основного кода) и замените строки 4-7 следующим кодом:

self._subclass_init(self)

Создание возможности присвоения подкласса классу более высокого уровня, когда имеется много/неизвестное количество подклассов.

Замените строку 6 следующим кодом:

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

Пример сценария, где это решение было бы полезно и где имя класса более высокого уровня должно быть невозможно получить:

Создается класс с именем "a" (class a:). У него есть подклассы, которым необходимо получить к нему доступ (родительский). Один подкласс называется "x1". В этом подклассе запускается код a.run_func().

Затем создается другой класс с именем "b", производный от класса "a" (class b(a):). После этого какой-то код запускает b.x1() (вызывая подфункцию "x1" из b, производного подкласса). Эта функция запускает a.run_func(), вызывая функцию "run_func" класса "a", а не функцию "run_func" своего родителя, "b" (как следует), потому что функция, которая была определена в класс "a" установлен для ссылки на функцию класса "a", поскольку он был его родителем.

Это вызовет проблемы (например, если функция a.run_func была удалена), и единственным решением без перезаписи кода в классе a.x1 будет переопределение подкласса x1 с обновленным кодом для всех классов, производных от класса "а", что, очевидно, будет сложно. и не стоит.

person Edward    schedule 31.01.2016
comment
спасибо за хороший комментарий к моему ответу. Я ничего не знал о тегах ответов сообщества вики, и теперь я удалил это! Так что спасибо и за указание на это. Возможно, вы захотите отредактировать свой ответ соответствующим образом, чтобы не запутать будущих читателей! - person mike rodent; 01.02.2016

Вы хотите использовать наследование, а не вложенные классы? То, что вы делаете, не имеет большого смысла в Python.

Вы можете получить доступ к некоторому_методу Outer, просто сославшись на Outer.some_method в методах внутреннего класса, но он не будет работать так, как вы ожидаете. Например, если вы попробуете это:

class Outer(object):

    def some_method(self):
        # do something

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

... вы получите TypeError при инициализации объекта Inner, потому что Outer.some_method ожидает получить экземпляр Outer в качестве первого аргумента. (В приведенном выше примере вы в основном пытаетесь вызвать some_method как метод класса Outer.)

person zenbot    schedule 08.01.2010
comment
Причина, по которой это, вероятно, не имеет смысла, заключается в том, что я намеренно взламываю. Для добавления пользовательских методов в QuerySet в Django требуется немного шаблонного кода, и я пытался найти умный способ сделать это с помощью python, который позволил мне создать шаблон шаблонного кода и просто написать соответствующие части в моем коде модели. - person T. Stone; 08.01.2010
comment
Извинения - я не знаю Django и поэтому не могу предложить способ шаблонизации шаблонного кода, но вы можете лаять не в том дереве, пытаясь вложить свои классы. Ваш внутренний класс ничего не получает от вашего внешнего класса. Все вложение его в Outer заставляет вас обращаться к нему через Outer.Inner, а не просто через Inner. - person zenbot; 08.01.2010

Другая возможность:

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 ()
person tsnorri    schedule 29.07.2011

Опоздал на вечеринку на несколько лет... но, чтобы расширить замечательный ответ @mike rodent, ниже я привел собственный пример, который показывает, насколько гибким является его решение и почему оно должно быть (или должен был быть был) принятым ответом.

Питон 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 # ....
)

Выход:

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

Опять же, замечательный ответ, реквизит для вас, Майк!

person tre-x    schedule 29.03.2020
comment
Я беспокоюсь о том, что происходит с вами pickle хотя, вы в конечном итоге с бесконечной рекурсией? (Я не на консоли Python, иначе я бы проверил это сам!) - person brandonscript; 26.04.2021

Развивая убедительное мышление @tsnorri, что внешний метод может быть статическим методом:

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

Теперь рассматриваемая линия должна работать к моменту ее фактического вызова.

Последняя строка в приведенном выше коде дает классу Inner статический метод, который является клоном статического метода Outer.


При этом используются две особенности Python: функции являются объектами и область действия является текстовой.

Обычно локальная область видимости ссылается на локальные имена текущей (текстовой) функции.

...или текущий класс в нашем случае. Таким образом, на объекты, «локальные» для определения внешнего класса (Inner и some_static_method), можно ссылаться непосредственно в этом определении.

person Bob Stein    schedule 20.10.2015

Что мы можем сделать, так это передать переменную self класса Outer во внутренний класс в качестве аргумента класса, а в разделе Outer init инициализировать внутренний класс с внешним self, переданным во внутренний.

class Outer:
    def __init__(self):
        self.somevalue=91
        self.Inner=self.Inner(self)
    def SomeMethod(self):
        print('This is Something from Outer Class')

    class Inner:
        def __init__(self,Outer)
            self.SomeMethod=Outer.SomeMethod
            self.somevalue=Outer.somevalue
    
        def SomeAnotherMethod(self):
            print(self.somevalue)
            self.SomeMethod()        

>>>f=Outer()
>>>f.Inner.SomeAnotherMethod() 
91
This is Something from Outer Class

Теперь после запуска этой функции она работает

person mits_v    schedule 28.05.2021

Вы можете создать класс, чтобы украсить внутренние классы. В данном случае @inner.

Так как это декоратор: Outer.A = inner(Outer.A). Как только ваш код требует Outer.A, он будет выполнен методом inner.__get__, который возвращает исходный класс (A) с новым набором атрибутов: A.owner = Outer.

Метод класса в классе A, в данном случае def add(cls, y=3), может использовать новый атрибут owner в return cls.owner.x + y + 1.

Строка setattr(owner, name, self.inner) прерывает дескриптор, потому что owner.name => Outer.A => A больше не является экземпляром внутреннего класса.

Надеюсь это поможет.

    class inner:
    
        def __init__(self, inner):
            self.inner = inner
    
        def __get__(self, instance, owner):
            print('__get__ method executed, only once... ')
            name = self.inner.__name__
            setattr(self.inner, 'owner', owner) 
            setattr(owner, name, self.inner) # breaks descriptor
            return self.inner #returns Inner
    
    class Outer:
        x = 1
    
        @inner
        class A:
    
            @classmethod
            def add(cls, y=3):
                return cls.owner.x + y + 1
    
    print(Outer.A.add(0)) # First time executes inner.__get__ method
    print(Outer.A.add(0)) # Second time not necessary.

    >> __get__ method executed, only once... 
    >> 2
    >> 2

person Pablo Pedro Espinoza Ravanal    schedule 26.11.2020

Это слишком просто:

Вход:

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

Выход

функция класса А1

функция класса B1

person Dhiman Ghosh    schedule 22.04.2020
comment
Хм, обратите внимание на вопрос и слова доступ к внешнему классу. По счастливому, довольно забавному совпадению ваш внешний и внутренний классы имеют метод func1. И, что не менее забавно, вы создаете A внутри конструктора B, который абсолютно НЕ является объектом внешнего класса A этого конкретного B. - person mike rodent; 28.04.2020