The Python docs say that the metaclass of a class can be any callable. All the examples I see use a class. Why not use a function? It's callable, and fairly simple to define. But it isn't working, and I don't understand why.
Here's my code:
class Foo(object):
def __metaclass__(name, base, dict):
print('inside __metaclass__(%r, ...)' % name)
return type(name, base, dict)
print(Foo.__metaclass__)
class Bar(Foo):
pass
print(Bar.__metaclass__)
Here's the output:
inside __metaclass__('Foo', ...)
<unbound method Foo.__metaclass__>
<unbound method Bar.__metaclass__>
The metaclass method is defined for both the parent and child classes. Why is it only getting called for the parent? (Yes, I tried using the classmethod and staticmethod decorators for my metaclass, neither works. Yes, this might seem to be a dup of Metaclass not being called in subclasses but they are a class, not a function, as a metaclass.)
The answer is in the precedence rules for __metaclass__
lookup:
The appropriate metaclass is determined by the following precedence rules:
- If
dict['__metaclass__']
exists, it is used.- Otherwise, if there is at least one base class, its metaclass is used (this looks for a
__class__
attribute first and if not found, uses its type).- Otherwise, if a global variable named
__metaclass__
exists, it is used.- Otherwise, the old-style, classic metaclass (
types.ClassType
) is used.
If we examine Foo.__class__
we find that it is <type 'type'>
, which is expected as your metaclass function called type
to construct Foo
.
__class__
is set by type
to the first parameter of type.__new__
, which is why in class metaclasses we call type.__new__(cls, name, bases, dict)
(or super(Metaclass, cls).__new__(cls, ...)
). However, we can't do that if the metaclass is a function:
>>> def __metaclass__(name, base, dict):
>>> print('inside __metaclass__(%r, %r, %r)' % (name, base, dict))
>>> return type.__new__(__metaclass__, name, base, dict)
>>> class Foo(object):
>>> __metaclass__ = __metaclass__
TypeError: Error when calling the metaclass bases
type.__new__(X): X is not a type object (function)
Similarly, if we try to set Foo.__class__
to your __metaclass__
it fails, as the __class__
attribute must be a class:
>>> Foo.__class__ = Foo.__metaclass__.__func__
TypeError: __class__ must be set to new-style class, not 'function' object
So, the reason to make metaclasses classes inheriting type
, as opposed to just callables, is to make them inheritable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With