Область вкладених класів?


116

Я намагаюся зрозуміти сферу застосування вкладених класів в Python. Ось мій приклад коду:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

Створення класу не завершується, і я отримую помилку:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Спроба inner_var = Outerclass.outer_varне працює. Я отримав:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Я намагаюся отримати доступ до статики outer_varз InnerClass.

Чи є спосіб це зробити?

Відповіді:


105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

Це не зовсім так, як подібні речі працюють на інших мовах, і використовується глобальний пошук замість того, щоб отримати доступ до нього outer_var. (Якщо ви зміните, до якого об'єкта пов'язане ім'я Outer, цей код буде використовувати цей об'єкт наступного разу, коли він буде виконаний.)

Якщо ви хочете, щоб усі Innerоб'єкти мали посилання на атрибут, Outerтому що outer_varце дійсно атрибут екземпляра:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

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

        @property
        def inner_var(self):
            return self.outer.outer_var

Зауважте, що класи вкладки є дещо рідкісними в Python, і це не означає автоматично будь-які особливі відносини між класами. Вам краще не гніздитися. (Ви все ще можете встановити атрибут класу на , Outerщоб Inner, якщо ви хочете.)


2
Можливо, буде корисно додати, з якою версією python буде працювати ваша відповідь.
AJ.

1
Я писав це на увазі 2.6 / 2.x, але, дивлячись на це, я не бачу нічого, що не працювало б так само в 3.x.

Я не зовсім розумію, що ви маєте на увазі в цій частині "(Якщо ви зміните, до якого об'єкта пов'язане ім'я Зовнішнє, то цей код буде використовувати цей об'єкт наступного разу, коли він буде виконаний.)" Чи можете ви допомогти мені зрозуміти?
батбрат

1
@batbrat, це означає, що посилання на Outerкожен раз переглядаються заново Inner.inner_var. Тож якщо ви відновите ім'я Outerдо нового об’єкта, Inner.inner_varпочне повертати цей новий об’єкт.
Феліпе

45

Я думаю, ви можете просто зробити:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Проблема, з якою ви зіткнулися, пов’язана з цим:

Блок - це фрагмент тексту програми Python, який виконується як одиниця. Нижче наведені блоки: модуль, тіло функції та визначення класу.
(...)
Діапазон визначає видимість імені в блоці.
(...)
Область імен, визначених у блоці класів, обмежена блоком класів; він не поширюється на кодові блоки методів - сюди входять генераторні вирази, оскільки вони реалізовані за допомогою функції функції. Це означає, що не вдасться:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Вищезазначене означає:
функціональне тіло - це блок коду, а метод - функція, тоді імена, визначені з функції функції, присутні у визначенні класу, не поширюються на тіло функції.

Перефразовуючи це для вашого випадку:
визначення класу є кодовим блоком, тоді імена, визначені з визначення внутрішнього класу, наявного у зовнішньому визначенні класу, не поширюються на визначення внутрішнього класу.


2
Дивовижний. Ваш приклад не вдається, стверджуючи, що "глобальне ім'я" a "не визначено". Однак заміщення розуміння списку [a + i for i in range(10)]успішно пов'язує Ab із очікуваним списком [42..51].
Джордж

6
@George Зауважте, що приклад з класом A не мій, це офіційний документ Python, на який я дав посилання. Цей приклад не вдається, і цей збір є тим, що хочеться показати в цьому прикладі. Насправді list(a + i for i in range(10))це list((a + i for i in range(10)))тобто list(a_generator). Кажуть, генератор реалізований з аналогічною сферою, ніж сфера функцій.
оченка

@George Для мене це означає, що функції діють по-різному, якщо вони є в модулі або в класі. У першому випадку функція виходить назовні, щоб знайти об'єкт, прив’язаний до вільного ідентифікатора. У другому випадку функція, тобто метод, не виходить за межі свого тіла. Функції в модулі та методи в класі насправді є двома видами об'єктів. Методи - це не просто функції на уроці. Це моя ідея.
оченка

5
@George: FWIW, ні list(...)виклик, ні розуміння не працюють в Python 3. Документація для Py3 також дещо відрізняється відбиттям цього. Тепер він говорить: " Обсяг імен, визначений у блоці класу, обмежений блоком класів; він не поширюється на кодові блоки методів - це включає розуміння та вирази генератора, оскільки вони реалізовані за допомогою області застосування функції " . ).
мартино

Мені цікаво, чому список (Aa + i для i в діапазоні (10)) також не працює, де я виправив Aa, я думаю, що A може бути глобальною назвою.
gaoxinge

20

Вам може бути краще, якщо ви просто не використовуєте вкладені класи. Якщо потрібно гніздо, спробуйте:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Або оголосити обидва класи, перш ніж вкладати їх:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Після цього ви можете, del InnerClassякщо вам потрібно.)


3

Найпростіше рішення:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Це вимагає від вас явного, але не вимагає великих зусиль.


NameError: ім'я 'OuterClass' не визначено - -1
Mr_and_Mrs_D

3
Сенс полягає в тому, щоб отримати доступ до нього з області зовнішнього класу - плюс подібна помилка виникла, якщо ви будете викликати це зі статичної області всередині Зовнішньої. Я б пропонував вам видалити цю публікацію
Mr_and_Mrs_D

1

У Python змінні об'єкти передаються як посилання, тому ви можете передати посилання зовнішнього класу до внутрішнього класу.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

2
Зверніть увагу, що у вас тут довідковий цикл, і в деяких сценаріях екземпляр цього класу не буде звільнений. Один із прикладів cPython, якщо ви визначили __del__ метод, збирач сміття не зможе обробляти еталонний цикл, і об’єкти будуть входити в нього gc.garbage. Наведений вище код, як є, не є проблематичним. Спосіб боротьби з нею - використання слабкого посилання . Ви можете прочитати документацію на slabref (2.7) або slabref (3.5)
guyarad

0

Усі пояснення можна знайти в Документації на Python The Tutorial

Для вашої першої помилки <type 'exceptions.NameError'>: name 'outer_var' is not defined. Пояснення:

Немає скорочень для посилання на атрибути даних (або інші методи!) Зсередини методів. Я вважаю, що це фактично збільшує читабельність методів: немає шансів плутати локальні та екземплярні змінні при огляді методу.

цитується з Підручника Python 9.4

Для вашої другої помилки <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Коли визначення класу залишається нормально (через кінець), створюється об’єкт класу.

цитується з Підручника Python 9.3.1

Тож коли ви намагаєтеся inner_var = Outerclass.outer_var, Quterclassще не створено, ось чомуname 'OuterClass' is not defined

Більш детальне, але виснажливе пояснення вашої першої помилки:

Хоча класи мають доступ до областей функцій, що додаються, вони, однак, не виконують функції охоплюючих областей до коду, вкладеного в клас: Python здійснює пошук вкладених функцій для імен, на які посилаються, але ніколи до класів, що додають. Тобто клас - це локальний діапазон і має доступ до локальних областей, але він не служить для локальної області застосування для подальшого вкладеного коду.

цитується з Learning.Python (5-й) .Mark.Lutz

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.