почему атрибуты суперкласса не доступны в пространстве имен текущего класса? - PullRequest
2 голосов
/ 25 января 2012

Пример:

class A:
    a = 1

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

z = B()
z.a # works fine
B.a # works fine

Почему x = a не разрешено? В любом другом контексте (доступ через экземпляр, доступ через имя подкласса) он работает нормально; но как-то внутри самого класса, это не работает.

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

Вот что я пытался (безуспешно) сделать:

class X:
  accelerate = compose(f1, f2, f3) # f1, f2, f3 are functions

class Y(X):
  move = compose(f4, f5)
  stop = f6

class Z(Y):
  action = compose(accelerate, stop)

class U(Y):
  action = compose(move, stop)

Эти классы вообще не были бы инициализированы; Я просто хотел использовать их для создания иерархии функций.

Ответы [ 3 ]

7 голосов
/ 25 января 2012

Когда вы пишете это:

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

Что Python делает, это создает новую область (хранится в словаре), выполняет все ваши определения, а затем в конце блока класса передает словарь вкласс type (если вы не установили другой метакласс) для создания нового класса.Это примерно эквивалентно следующему:

B = type('B', (A,), classdict_from_scope)

Прежде чем перейти к этому этапу, класс B не существует, не говоря уже о наличии базовых классов для наследования атрибутов.Учтите, что у вас может быть несколько базовых классов, и порядок разрешения имен в этих классах является сложным и зависит от их полного набора и всех их баз;этот порядок не будет определен до тех пор, пока ваш дочерний класс B не будет фактически создан.

Это означает, что когда Python начинает выполнять x = a, он находит a вне области действия и не выполняет атрибутпоиск класса или экземпляра, поэтому он не может следовать протоколу разрешения имен для поиска альтернативной привязки.Поэтому все, что он может сделать, это выдать ошибку.

Так вот , почему Python работает именно так.Что вы можете с этим поделать?

У вас есть два основных варианта.

  1. Вы можете указать класс, в котором вы хотите явно искать атрибуты.
  2. Выможет искать атрибуты в вашем подклассе после его создания.

Обратите внимание, что (1) не так уж и плохо, если вы используете только одиночное наследование;вы можете просто посмотреть все атрибуты в A;если они определены только в родительском классе A, он все равно найдет их, поэтому вам на самом деле не нужно знать, где в иерархии они определены.

Если у вас множественное наследование, тогдавам нужно знать хотя бы, в какой иерархии содержится атрибут, который вы хотите найти.Если это сложно или невозможно, вы можете использовать опцию (2), которая будет выглядеть примерно так:

class B(A):
    b = 2
    y = b

B.x = B.a

Это выглядит немного уродливо, но создает идентичный класс B, как если бывы создали x внутри блока класса, и переходный класс B без x никогда не будет виден никаким другим кодом, если вы поместите присваивание B.x непосредственно после блока класса.(Хотя может не иметь идентичных результатов, если вы используете декораторы классов)

2 голосов
/ 25 января 2012

Член класса определения не обязательно должны начинаться с префикса имени класса (так как они находятся в блоке class), но accesses делает (потому что неясно, что вы хотитедля доступа).

Вы можете получить доступ к b, потому что он уже находится в локальной области этого блока кода (он был определен там).a нет;он присутствует только после того, как класс полностью определен.

Зачем вам нужно x = a?Почему вы основываете значение одной переменной-члена на другой?Если вы действительно хотите, вы можете использовать функцию __init__ класса для копирования значения (поскольку подкласс полностью определяется к моменту запуска __init__).

1 голос
/ 25 января 2012

Ответ Бена вполне объясним, что происходит.

Поскольку вы работаете в Python 3, в метаклассы Python добавлена ​​функция, позволяющая выполнять то, что вы хотите сделать - AЛокальный словарь класса может быть обновлен (не хакерским способом, как явный вызов "locals ()") перед анализом тела класса.

Все, что нужно, - это использовать метод __prepare__ дляметакласс - ему передается имя класса и его базы в виде кортежа, и ожидается, что он возвратит объект словаря, который будет использоваться для анализа класса тела:

class MetaCompose(type):
    @staticmethod
    def __prepare__(name, bases):
        dct = {}
        for base in reversed(bases):
            dct.update(base.__dict__)
        return dct


class A:
    a = 1

class B(A, metaclass=MetaCompose):
    x = a


B.x

(ср. http://docs.python.org/py3k/reference/datamodel.html#customizing-class-creation)

...