21

In order to set metaclass of a class, we use the __metaclass__ attribute. Metaclasses are used at the time the class is defined, so setting it explicitly after the class definition has no effect.

This is what happens when I try to set metaclasses explicitly;

>>> 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'

The best idea I can think of is to re-define whole class and add the __metaclass__ attribute dynamically somehow. Or do you know a better way set metaclass after the class definition?

Utku Zihnioglu
  • 4,714
  • 3
  • 38
  • 50

5 Answers5

16

You can change the metaclass after class creation the same way that you can change the class of an object, however you'd have a lot of issues. For starters, the initial metaclass needs to be different from type, the __init__ and __new__ of the new metaclass won't be called (though you can manually call __init__ or a method that performs __init__'s job).

Possibly the least troublesome way to change the metaclass is to recreate the class again from scratch:

B = MetaClass(B.__name__, B.__bases__, B.__dict__)

But if you insist on changing the metaclass dynamically, you first need to define B with a temporary custom metaclass:

class _TempMetaclass(type):
    pass

class B:
    __metaclass__ = _TempMetaclass # or: type('temp', (type, ), {})

Then you can define the metaclass like that:

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

And then do something like:

B.__class__ = MetaClass
MetaClass._actual_init(B, B.__name__, B.__bases__, B.__dict__)

You also need to make sure that all the initialization of the class is done _actual_init. You can also add a classmethod to the metaclass that changes the metaclass for you.

Both solutions have the slight shortcoming that B's bases would be limited - they need to be compatible with both the original and the new metaclass, but I guess that's not an issue in your case.

Rosh Oxymoron
  • 20,355
  • 6
  • 41
  • 43
6

The purpose of metaclasses isn't to modify attribute access, but to customize class creation. The __metaclass__ attribute defines the callable used to construct a type object from a class definition, defaulting to type(). Consequently this attribute is only evaluated at class creation. It obviously doesn't make any sense to change it at any later point, because you can't customize class creation for classes that have already been created. See Python Language Reference, 3.4.3 Customizing class creation for details.

Metaclasses are simply not the right tool for what you obviously want to do. If you simply want to add a new attribute to an already existing class, why don't you just assign it?

2

You can think that a metaclass describes what the class statement does. That is why setting the metaclass later is impossible: The classes code has already been converted to a type object, it's too late to change it.

You can simply subclass your original type to add a metaclass:

class C(B):
    __metaclass__=MetaClass
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
-4

Why do you need this? I don't believe this is possible, but if you can explain the use case, I can probably suggest an alternative.

Along the lines of re-defining the whole class, what you could do is this:

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

Then just copy all the old methods over:

for item in dir(OldClass):
    setattr(NewClass, item, getattr(OldClass, item))
tangentstorm
  • 7,183
  • 2
  • 29
  • 38
  • Setting the attributes as `{'__metaclass__':MetaClass}` does not implement the MetaClass. – Utku Zihnioglu Feb 25 '11 at 18:00
  • Hrm. I guess that kind of makes sense. You'd just do MetaClass(...) something, wouldn't you? I've never tried to use a metaclass that way. Time to experiment... – tangentstorm Feb 25 '11 at 18:04
-5

I am unable to test this out right now (I don't have access to Python in my machine right now :-(), but unless metaclass is imutable, perhaps you can try something like this:

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

Again, I can't test it right now so I apologize if this is an invalid answer, just trying to help. :)

João Neves
  • 937
  • 1
  • 6
  • 13
  • Not only it do not work but it is absurd, as is the question. Metaclass are used to hook something at class creation, changing it after class has been created do not make sense. – gb. Aug 27 '13 at 03:03