Есть ли способ установить метакласс после определения класса? - PullRequest
13 голосов
/ 25 февраля 2011

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

Ответы [ 5 ]

11 голосов
/ 25 февраля 2011

Вы можете изменить метакласс после создания класса так же, как вы можете изменить класс объекта, однако у вас будет много проблем. Для начала, начальный метакласс должен отличаться от 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 будут ограничены - они должны быть совместимы как с оригинальным, так и с новым метаклассом, но я думаю, что это не проблема в вашем случае.

5 голосов
/ 25 февраля 2011

Цель метаклассов состоит не в том, чтобы изменить доступ к атрибутам, а в том, чтобы настроить создание классов.Атрибут __metaclass__ определяет вызываемый объект, используемый для создания объекта типа из определения класса, по умолчанию type().Следовательно, этот атрибут оценивается только при создании класса .Очевидно, что нет смысла менять его в более поздний момент, потому что вы не можете настроить создание классов для классов, которые уже были созданы.Подробнее см. Справочник по языку Python, 3.4.3 Настройка создания класса .

Метаклассы просто не подходят для того, что вы явно хотите сделать.Если вы просто хотите добавить новый атрибут в уже существующий класс, почему бы вам просто не присвоить его?

2 голосов
/ 25 февраля 2011

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

Вы можете просто создать подкласс своего исходного типа, чтобы добавить метакласс:

class C(B):
    __metaclass__=MetaClass
0 голосов
/ 25 февраля 2011

Зачем тебе это? Я не верю, что это возможно, но если вы можете объяснить вариант использования, я, вероятно, могу предложить альтернативу.

В соответствии с переопределением всего класса, вы могли бы сделать следующее:

import types
NewClass = types.ClassType('NewClass', (object, ), {'__metaclass__':MetaClass})

Затем просто скопируйте все старые методы:

for item in dir(OldClass):
    setattr(NewClass, item, getattr(OldClass, item))
0 голосов
/ 25 февраля 2011

Я не могу проверить это прямо сейчас (у меня сейчас нет доступа к Python на моей машине :-(), но если метакласс не изменяем, возможно, вы можете попробовать что-то вроде этого :

>>> class B:
        pass
>>> setattr(B, "__metaclass__", MetaClass)

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

...