2012-01-15 1 views
6

У меня есть странный и необычный прецедент для метаклассов, где я хотел бы изменить __metaclass__ базового класса после того, как он будет определен таким образом, чтобы его подклассы автоматически использовали новый __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) 

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

EDIT: на основе предложений, сделанных jsbueno, я заменил строку Foo.__metaclass__ = MetaSub со следующей строкой, которая делала именно то, что я хотел:

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

ответ

2

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

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

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

Foo.__metaclass__ = MetaSub

вы:

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

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

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

+0

Спасибо за подсказку, что не сделал именно то, что я хотел, так как я все еще хочу, чтобы подклассы «Foo» имели метакласс, но я отредактировал свой вопрос, чтобы включить решение, которое делало то, что мне нужно, на основе вашего предложение. –

3

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

Соответствующего метаклассом определяется следующая очередностью правил:

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

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

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

1

Подклассы используют __metaclass__ своих родителей.

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

+0

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

+0

@EliCourtwright Возможно, вы можете свернуть собственный класс, который делегирует стороннему модулю, а не наследует его. Это должно дать вам полный контроль над процессом создания класса при максимальном повторном использовании кода. –

 Смежные вопросы

  • Нет связанных вопросов^_^