Подтвердить что ты не робот

Есть ли способ установить метакласс после определения класса?

Чтобы установить метакласс класса, мы используем атрибут __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__. Или вы знаете, как лучше установить метакласс после определения класса?

4b9b3361

Ответ 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)

Опять же, я не могу проверить это прямо сейчас, поэтому прошу прощения, если это недопустимый ответ, просто пытаясь помочь.:)