Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
361 views
in Technique[技术] by (71.8m points)

python - Is there a way to set metaclass after the class definition?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...