Объем вложенных классов?

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 будет работать ваш ответ.
Эй Джей.
1
Я написал это, имея в виду 2.6 / 2.x, но, глядя на это, я не вижу ничего, что не работало бы так же в 3.x.
Я не совсем понимаю, что вы имеете в виду в этой части: «(Если вы измените объект, к которому привязано имя Outer, тогда этот код будет использовать этот объект при следующем выполнении.)« Не могли бы вы помочь мне понять?
batbrat
1
@batbrat означает, что ссылка на Outerкаждый раз просматривается заново Inner.inner_var. Поэтому, если вы повторно привяжете имя Outerк новому объекту, Inner.inner_varначнется возврат этого нового объекта.
Felipe
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

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

Перефразируя это для вашего случая:
определение класса - это блок кода, тогда имена, определенные из определения внутреннего класса, присутствующего в определении внешнего класса, не распространяются на определение внутреннего класса.

Eyquem
источник
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). Они говорят, что генератор реализован с той же областью действия, что и объем функций.
eyquem 08
@George Для меня это означает, что функции действуют по-разному в зависимости от того, находятся ли они в модуле или в классе. В первом случае функция выходит наружу, чтобы найти объект, привязанный к свободному идентификатору. Во втором случае функция, то есть метод, не выходит за пределы своего тела. Функции в модуле и методы в классе на самом деле являются объектами двух типов. Методы - это не просто функции в классе. Это моя идея.
eyquem 08
5
@George: FWIW, ни list(...)вызов, ни понимание не работают в Python 3. Документация для Py3 также немного отличается, отражая это. Он теперь говорит , что « Объем имен , определенные в классе блоке ограничивается блоком класса, он не распространяется на блоки кода методов - это включает в себя постижения . И выражение генератора , так как они реализованы с использованием функцией сферы » (курсив моего ).
Мартино
Мне любопытно, почему список (Aa + i for i in range (10)) также не работает, где я исправил a с помощью 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. Приведенный выше код, как есть, не вызывает проблем. Чтобы справиться с этим, используйте слабую ссылку . Вы можете прочитать документацию по weakref (2.7) или weakref (3.5)
гайарад 01
0

Все объяснения можно найти в документации Python The Python Tutorial

За вашу первую ошибку <type 'exceptions.NameError'>: name 'outer_var' is not defined. Объяснение таково:

Нет сокращений для ссылки на атрибуты данных (или другие методы!) Изнутри методов. Я считаю, что это на самом деле увеличивает удобочитаемость методов: при просмотре метода невозможно спутать локальные переменные и переменные экземпляра.

цитируется из The Python Tutorial 9.4

За вашу вторую ошибку <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Когда определение класса остается обычным (через конец), создается объект класса.

цитируется из The Python Tutorial 9.3.1

Итак, когда вы пытаетесь inner_var = Outerclass.outer_var, он Quterclassеще не создан, поэтомуname 'OuterClass' is not defined

Более подробное, но утомительное объяснение вашей первой ошибки:

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

цитата из Learning.Python (5th) .Mark.Lutz

Энди Го
источник