Як отримати доступ до зовнішнього класу з внутрішнього класу?


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
Внутрішній клас, звичайно, передбачає взаємозв'язок із зовнішнім класом, як правило, це пов'язано із передбачуваною сферою використання внутрішнього класу або організаційним простором імен.
Проникність

61

Ви намагаєтесь отримати доступ до екземпляра класу Зовнішнього з внутрішнього екземпляра класу. Тож просто використовуйте фабричний метод для побудови внутрішнього екземпляра та передачі йому зовнішнього екземпляра.

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

Це саме правильна відповідь - її слід вибрати як правильну, оскільки вона забезпечує вирішення питання ОП - велике спасибі!
Даніель

28

можливо, я божевільний, але це справді здається дуже легким - справа полягає в тому, щоб зробити свій внутрішній клас методом зовнішнього класу ...

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 Оскільки на ваш коментар (до моєї відповіді), я відредагував свою відповідь, щоб видалити опис "wiki wiki". Як і ви, я не маю жодних знань про відповіді "wiki community", тому добре, що ви виправили свою помилку.
Едвард

@mikerodent Крім того, не ображайте, але я не думаю, що відповіді на "wiki-спільноту" мають тег "wiki-спільноти", вони повинні бути просто відповіддю. Напевно, у Stack Overflow буде сторінка довідки з описом відповідей "wiki community", якщо вам це цікаво.
Едвард

4

Ви маєте на увазі використовувати спадщину, а не вкладати такі класи? Те, що ви робите, не має сенсу в Python.

Ви можете отримати доступ до Outerметоду some_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, який дозволив мені сформулювати шаблонний шаблон і просто написати відповідні частини в моєму коді моделі.
T. Stone

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

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)

Я впевнений, що можна якось написати декоратора для цього чи чогось іншого

пов'язані: Яка мета внутрішніх класів пітона?


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«s прекрасний відповідь, я надав свій власний приклад нижче , який показує, наскільки гнучкий його рішення, і чому це повинно бути (або має бути було) прийняте відповідь.

Пітон 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.