The strangely behaving code (tested with Python 2.7.3):
class Meta1(type):
def __new__(mcl, name, bases, attrs):
print "Hello Meta1.__new__ "
return super(Meta1, mcl).__new__(mcl, name, bases, attrs)
class Meta2(type):
def __new__(mcl, name, bases, attrs):
print "Hello Meta2.__new__ "
return super(Meta2, mcl).__new__(
type, # looks to cause all strange behavior, but anyway pass type here, not mcl
name, bases, attrs)
print "Declaring BaseClass1"
class BaseClass1(object):
__metaclass__ = Meta1
print "-----------------------"
print "Declaring BaseClass2"
class BaseClass2(BaseClass1):
__metaclass__ = Meta2
print "-----------------------"
print BaseClass2.__class__
Its' output:
Declaring BaseClass1
Hello Meta1.__new__
-----------------------
Declaring BaseClass2
Hello Meta2.__new__
Hello Meta1.__new__ # WHY WAS IT INVOKED?
-----------------------
<class '__main__.Meta1'>
Questions about the code:
Why the class BaseClass2 is defined without any issues even though the __metaclass__
attribute for BaseClass2 is set to Meta2 and for its' parent class BaseClass1 the __metaclass__
attribute is set to Meta1, and neither Meta1 no Meta2 is a subclass of the another class?
Why at the BaseClass2 definition both Meta2.__new__
and Meta1.__new__
are called?
In which circumstances methods in the metaclasses of the parent classes are invoked?
Long story:
While trying to understand how metaclasses in our project work I crafted the code which can be found above. (The project uses Python 2.7.3, and it looks that the metaclasses use in the project is sound since they are used for providing API to the users and metaclasses do quite a lot of things for the user under the hood.)
In the first place I was trying to find the documentation on how the metaclasses work with inheritance. The following (quite old but looks to be valid for Python 2.7) article by Guido van Rossum shed some light on how metaclass is picked in the case of inheritance, what are the requirements to the metaclass of the subling class and minor tricks which can be performed by Python when choosing the metaclass for the sibling class: https://www.python.org/download/releases/2.2.3/descrintro/. This and other writings which I have read on the metaclasses in Python don't explain the behavior I am observing. I guess reading Python interpreter code will shed the light but I believe in the power of documentation and hope that this extreme measure can be avoided. Any answers/pointers to the materials which describe the code behavior observed are welcome.
After a lot of looking around, I think I found the answer. The Python 3 documentation has one section that says this.
3.3.3.3. Determining the appropriate metaclass
The appropriate metaclass for a class definition is determined as follows:
- 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 usedThe most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e.
type(cls)
) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail withTypeError
.
I think this still also applies to Python 2 (v2.7 anyway) even though I can't find anything like the above in its documentation.
The reason why the BaseClass2
definition invokes both Meta2.__new__()
and Meta1.__new__()
is simple — Meta2.__new__()
explicity invokes it via its call to super()
. However to get it to work properly, you'd also need to change Meta2.__new__()
so it returns super(Meta2, mcl).__new__(mcl, name, bases, attrs)
instead of super(Meta2, mcl).__new__(type, name, bases, attrs)
.
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