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


102

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

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класса из класса?


Почему вы это делаете? Что плохого в простых отношениях со сверстниками? Вы что-то пытаетесь «скрыть»?
S.Lott

1
Примером этого сценария может быть класс с подклассами, которым требуется доступ к внешнему классу, как обычно, но затем необходимо создать другой класс (верхний уровень), производный от первого класса. В этом случае подклассы второго класса будут пытаться получить доступ к родительскому классу, используя self.<original_parent_name>и получить исходный класс, а не новый класс, от которого они являются подклассом . Я надеюсь, что люди, читающие это, смогут представить себе этот сложный сценарий и понять суть подобных вопросов.
Эдвард

1
Дублируйте stackoverflow.com/questions/2278426, и у него есть хороший ответ.
xmedeko

Ответы:


68

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

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

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


1
Хм, Python более резвый, чем Java / C ++ ... см. Мой ответ ниже. Если мы расщепляем волосы, как это обычно бывает, я не смог бы точно сказать вам, считается ли мой «вложенный класс внутри метода» внутренним классом. Однако на этом этапе я должен вызвать утиную типизацию: если он делает все, что может сделать внутренний класс ... с точки зрения Питона, вероятно, пора наскучить с расщеплением волосков,
Майк грызун

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

67

Вы пытаетесь получить доступ к экземпляру класса 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()

Это точно правильный ответ - его следует выбрать как правильный ответ, поскольку он дает решение вопроса ОП - большое спасибо!
Дэниел

29

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

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

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

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

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

даже немного вытолкните лодку и расширите этот внутренний класс (NB, чтобы заставить super () работать, вам нужно изменить подпись класса mooble на "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()

потом

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

1
У меня это работает. Как именно называется эта конструкция? Заводская функция? Закрытие?
nakedfanatic

Я понятия не имею, как это называется ... но могу ли я предположить, что причина, по которой другие плакаты не видели этого, заключается в том, что, возможно, не было полностью оценено, что большинство вещей в Python не являются священными, включая "себя «(произвольное имя) и классы - это« объекты первого класса », что, кажется, означает, что вы можете манипулировать ими весьма возмутительными способами,
Майк грызун

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

1
@mikerodent Потому что к вашему комментарию (к моему ответу) я отредактировал свой ответ, чтобы удалить описание "вики сообщества". Как и вы, я ничего не знаю об ответах «вики сообщества», так что хорошо, что вы исправили свою ошибку.
Эдвард

@mikerodent Кроме того, без обид, но я не думаю, что ответы «вики сообщества» имеют тег «вики сообщества», они должны быть просто типом ответа. Вероятно, будет справочная страница с описанием ответов «вики-сообщества» на Stack Overflow, если вам это интересно.
Эдвард

4

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

Вы можете получить доступ к Outersome_method, просто сославшись на 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.)


1
Причина, по которой это, вероятно, не имеет смысла, состоит в том, что я намеренно хакерю. Для добавления пользовательских методов в QuerySet в Django требуется немного шаблонного кода, и я пытался найти умный способ сделать это с помощью python, который позволил мне шаблонизировать шаблонный код и просто написать соответствующие части в моем коде модели.
Т. Стоун

Приносим извинения - я не знаю Django и поэтому не могу предложить способ шаблонизации шаблонного кода, но вы можете лаять не на то дерево, пытаясь вложить свои классы. Ваш внутренний класс ничего не получает от вашего внешнего класса. Все, что делает его вложение в Outer, заставляет вас обращаться к нему через Outer.Inner, а не просто Inner.
zenbot 08

3

Вы можете легко получить доступ к внешнему классу с помощью метакласса: после создания внешнего класса проверьте его атрибут 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!

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


3

Я создал код 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.run_funcбыла удалена), и единственным решением без переписывания кода в классе a.x1было бы переопределение подкласса x1с обновленным кодом для всех классов, производных от класса «a», что, очевидно, было бы сложно и не стоит Это.


спасибо за хороший комментарий к моему ответу. Я ничего не знал о тегах «Ответы сообщества вики», и теперь их удалили! Так что спасибо также за указание на это. Возможно, вы захотите соответствующим образом отредактировать свой ответ, чтобы не запутать будущих читателей!
Майк грызун

3

Я нашел это .

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

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?


2

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

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

Расширяя убедительное мышление @ 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

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

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


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

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

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


1

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

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

Вывод:

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

Опять же, прекрасный ответ, спасибо, Майк!


-2

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

Вход:

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

Вывод

класс A func1

класс B func1


2
Хм, обратите внимание на вопрос и слова доступа к внешнему классу . По счастливому, довольно забавному совпадению, ваш внешний и внутренний классы имеют метод func1. И, что не менее забавно, вы создаете Aвнутри конструктора B, который абсолютно НЕ является Aобъектом внешнего класса этого конкретного B.
Майк грызун
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.