Есть ли способ установить метакласс после определения класса?
Чтобы установить метакласс класса, мы используем атрибут __metaclass__
. Метаклассы используются во время определения класса, поэтому установка его явно после определения класса не имеет никакого эффекта.
Это то, что происходит, когда я пытаюсь установить метаклассы явно,
>>> class MetaClass(type):
def __new__(cls, name, bases, dct):
dct["test_var"]=True
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
super(MetaClass, cls).__init__(name, bases, dct)
>>> class A:
__metaclass__=MetaClass
>>> A.test_var
True
>>> class B:
pass
>>> B.__metaclass__=MetaClass
>>> B.test_var
Traceback (most recent call last):
File "<pyshell#20>", line 1, in <module>
B.test_var
AttributeError: class B has no attribute 'test_var'
Лучшей идеей, о которой я могу думать, является переопределение всего класса и динамическое добавление атрибута __metaclass__
. Или вы знаете, как лучше установить метакласс после определения класса?
Ответы
Ответ 1
Вы можете изменить метакласс после создания класса так же, как вы можете изменить класс объекта, однако у вас будет много проблем. Для начала исходный метакласс должен отличаться от type
, __init__
и __new__
нового метакласса не будут вызываться (хотя вы можете вручную вызвать __init__
или метод, выполняющий задание __init__
).
Возможно, наименее сложный способ изменить метакласс - снова воссоздать класс с нуля:
B = MetaClass(B.__name__, B.__bases__, B.__dict__)
Но если вы настаиваете на динамическом изменении метакласса, сначала вам нужно определить B с временным пользовательским метаклассом:
class _TempMetaclass(type):
pass
class B:
__metaclass__ = _TempMetaclass # or: type('temp', (type, ), {})
Затем вы можете определить метакласс следующим образом:
class MetaClass(type):
def __init__(cls, *a, **kw):
super(MetaClass, cls).__init__(*a, **kw)
cls._actual_init(*a, **kw)
def _actual_init(cls, *a, **kw):
# actual initialization goes here
И затем сделайте что-то вроде:
B.__class__ = MetaClass
MetaClass._actual_init(B, B.__name__, B.__bases__, B.__dict__)
Вам также необходимо убедиться, что вся инициализация класса выполнена _actual_init
. Вы также можете добавить classmethod
в метакласс, который изменит метакласс для вас.
Оба решения имеют небольшой недостаток, что базы B будут ограничены - они должны быть совместимы как с оригиналом, так и с новым метаклассом, но я думаю, что это не проблема в вашем случае.
Ответ 2
Цель метаклассов - не изменять доступ к атрибутам, а настраивать создание класса. Атрибут __metaclass__
определяет вызывающий, используемый для построения объекта типа из определения класса, по умолчанию - type()
. Следовательно, этот атрибут оценивается только при создании класса. Очевидно, что нет смысла менять его в любой момент, потому что вы не можете настроить создание классов для уже созданных классов. Подробнее см. Справочник по языку Python, 3.4.3 Настройка создания класса.
Метаклассы - это просто не правильный инструмент для того, что вы, очевидно, хотите сделать. Если вы просто хотите добавить новый атрибут в уже существующий класс, почему бы вам просто не назначить его?
Ответ 3
Вы можете думать, что метакласс описывает, что делает оператор class
. Вот почему установка метакласса позже невозможна: код классов уже преобразован в объект типа, слишком поздно, чтобы изменить его.
Вы можете просто подклассифицировать свой оригинальный тип, чтобы добавить метакласс:
class C(B):
__metaclass__=MetaClass
Ответ 4
Зачем вам это нужно? Я не считаю, что это возможно, но если вы можете объяснить вариант использования, я, вероятно, могу предложить альтернативу.
Вдоль линий переопределения всего класса вы можете сделать следующее:
import types
NewClass = types.ClassType('NewClass', (object, ), {'__metaclass__':MetaClass})
Затем просто скопируйте все старые методы:
for item in dir(OldClass):
setattr(NewClass, item, getattr(OldClass, item))
Ответ 5
Я не могу проверить это прямо сейчас (у меня нет доступа к Python на моей машине прямо сейчас:-(), но если метакласс не может быть imputable, возможно, вы можете попробовать что-то вроде это:
>>> class B:
pass
>>> setattr(B, "__metaclass__", MetaClass)
Опять же, я не могу проверить это прямо сейчас, поэтому прошу прощения, если это недопустимый ответ, просто пытаясь помочь.:)