Background
The Python 3 documentation clearly describes how the metaclass of a class is determined:
- if no bases and no explicit metaclass are given, then type() is used
- if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
- if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used
Therefore, according to the second rule, it is possible to specify a metaclass using a callable. E.g.,
class MyMetaclass(type):
pass
def metaclass_callable(name, bases, namespace):
print("Called with", name)
return MyMetaclass(name, bases, namespace)
class MyClass(metaclass=metaclass_callable):
pass
class MyDerived(MyClass):
pass
print(type(MyClass), type(MyDerived))
Question 1
Is the metaclass of MyClass
: metaclass_callable
or MyMetaclass
? The second rule in the documentation says that the provided callable "is used directly as the metaclass". However, it seems to make more sense to say that the metaclass is MyMetaclass
since
MyClass
andMyDerived
have typeMyMetaclass
,metaclass_callable
is called once and then appears to be unrecoverable,- derived classes do not use (as far as I can tell)
metaclass_callable
in any way (they useMyMetaclass
).
Question 2
Is there anything you can do with a callable that you can't do with an instance of type
? What is the purpose of accepting an arbitrary callable?
Well, the
type
is of courseMyMetaClass
.metaclass_callable
is initially 'selected' as the metaclass since it's been specified in themetaclass
kwarg and as such, it's__call__
(a simple function call) is going to be performed.It just so happens that calling it will
print
and then invokeMyMetaClass.__call__
(which callstype.__call__
since__call__
hasn't been overridden forMyMetaClass
). There the assignment ofcls.__class__
is made toMyMetaClass
.Yes, it is only initially invoked and then hands control over to
MyMetaClass
. I'm not aware of any class attribute that keeps that information around.Nope, if no
metaclass
is explicitly defined, the best match for the metaclasses ofbases
(hereMyClass
) will be used (resulting inMyMetaClass
).As for question
2
, pretty sure everything you can do with a callable is also possible by using an instance of type with__call__
overridden accordingly. As to why, you might not want to go full blown class-creation if you simply want to make minor changes when actually creating a class.Regarding your first question the metaclass should be
MyMetaclass
(which it's so):The reason is that if the metaclass is not an instance of type python calls the methaclass by passing these arguments to it
name, bases, ns, **kwds
(seenew_class
) and since you are returning your real metaclass in that function it gets the correct type for metaclass.And about the second question:
There is no special purpose, it's actually the nature of metaclasses which is because that making an instance from a class always calls the metaclass by calling it's
__call__
method:Which means that you can pass any callable as your metaclass. So for example if you test it with a nested function the result will still be the same:
For more info here is how Python crates a class:
It calls the
new_class
function which it callsprepare_class
inside itself, then as you can see inside theprepare_class
python calls the__prepare__
method of the appropriate metaclass, beside of finding the proper meta (using_calculate_meta
function ) and creating the appropriate namespace for the class.So all in one here is the hierarchy of executing a metacalss's methods:
__prepare__
1__call__
__new__
__init__
And here is the source code:
1. Note that it get called implicitly inside the new_class function and before the return.
Concerning question 1, I think the "metaclass" of a class
cls
should be understood astype(cls)
. That way of understanding is compatible with Python's error message in the following example:I.e., according to the error message, the metaclass of a class is a class, even though the callable used to construct the class can be just anything.
Concerning the second question, indeed with a subclass of type used as a metaclass, you can do the same as with any other callable. In particular, it is possible that it yields something that is not its instance:
As to why Python gives the freedom of using any callable: The previous example shows that it is actually irrelevant whether the callable is a type or not.
BTW, here is a fun example: It is possible to code metaclasses that, themselves, have a metaclass different from
type
---let's call it a metametaclass. The metametaclass implements what happens when a metaclass is called. In that way, it is possible to create a class with two bases whose metaclasses are not subclass of each other (compare with Python's error message in the example above!). Indeed, only the metaclass of the resulting class is subclass of the metaclass of the bases, and this metaclass is created on the fly:What is less fun: The preceding example won't work in Python 3. If I understand correctly, Python 2 creates the class and checks whether its metaclass is a subclass of all its bases, whereas Python 3 first checks whether there is one base whose metaclass is superclass of the metaclasses of all other bases, and only then creates the new class. That's a regression, from my point of view. But that shall be the topic of a new question that I am about to post...
Edit: The new question is here