Почему я не могу изменить атрибут __metaclass__ класса? - PullRequest
6 голосов
/ 15 января 2012

У меня есть странный и необычный вариант использования для метаклассов, где я хотел бы изменить __metaclass__ базового класса после его определения, чтобы его подклассы автоматически использовали новый __metaclass__. Но это странно не работает:

class MetaBase(type):
    def __new__(cls, name, bases, attrs):
        attrs["y"] = attrs["x"] + 1
        return type.__new__(cls, name, bases, attrs)

class Foo(object):
    __metaclass__ = MetaBase
    x = 5

print (Foo.x, Foo.y) # prints (5, 6) as expected

class MetaSub(MetaBase):
    def __new__(cls, name, bases, attrs):
        attrs["x"] = 11
        return MetaBase.__new__(cls, name, bases, attrs)

Foo.__metaclass__ = MetaSub

class Bar(Foo):
    pass

print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12)

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

РЕДАКТИРОВАТЬ: Основываясь на предложении jsbueno, я заменил строку Foo.__metaclass__ = MetaSub следующей строкой, которая сделала именно то, что я хотел:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__))

Ответы [ 3 ]

3 голосов
/ 15 января 2012

Проблема в том, что атрибут __metaclass__ не используется при наследовании, в отличие от того, что вы могли ожидать. «Старый» метакласс не вызывается ни для Bar. В документах говорится о том, как найти метакласс:

Соответствующий метакласс определяется следующим приоритетом правила:

  • Если dict ['__ metaclass__'] существует, он используется.
  • В противном случае, если существует хотя бы один базовый класс, используется его метакласс (сначала выполняется поиск атрибута __class__ и, если он не найден, использует свой тип).
  • В противном случае, если существует глобальная переменная с именем __metaclass__, она используется.
  • В противном случае используется классический метакласс старого типа (types.ClassType).

То, что фактически используется в качестве метакласса в вашем классе Bar, содержится в атрибуте __class__ родителя, а не в атрибуте __metaclass__ родителя.

Дополнительную информацию можно найти по этому ответу StackOverflow .

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

Информация метакласса для класса используется в момент его создания (либо анализируется как блок класса, либо динамически, с явным вызовом метакласса).Его нельзя изменить, потому что метакласс обычно вносит изменения во время создания класса - созданный тип класса является метаклассом.Его атрибут __metaclass__ не имеет значения после его создания.

Однако можно создать копию данного класса и сделать так, чтобы копия имела меткласс, отличный от исходного класса.

На вашем примере, если вместо того, чтобы делать:

Foo.__metaclass__ = MetaSub

, вы делаете:

Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

Вы добьетесь того, что хотели.Новый Foo для всех эффектов равен своему предшественнику, но с другим метаклассом.

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

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

Подклассы используют метакласс __ __ своего родителя.

Решение вашего сценария использования состоит в том, чтобы запрограммировать метакласс родителя __ __ так, чтобы он имел различные поведения дляродитель, чем для его подклассов.Возможно, он будет проверять словарь классов на предмет переменной класса и реализовывать различные варианты поведения в зависимости от ее значения (этот метод тип использует для контроля того, предоставляется ли экземпляру словарь в зависимости от того, * 1009 или нет)* ___ слотов __ определяется).

...