I noticed that I couldn't use __init_subclass__
with Django model classes in quite the way I wanted to. It seems that the metaclass hasn't finished creating a child class by the time that a parent class' __init_subclass__
method is run. While I understand what the issue is, and can circumvent it by making a custom metaclass, what I don't understand is why!
In my head, I tend to think that any calls like __new__
should be done before any calls like __init__
happen. But this isn't the case for metaclasses and __init_subclass__
, as demonstrated here:
class MetaMeta(type):
print('parsing MetaMeta')
def __call__(cls, *args, **kwargs):
print('entering MetaMeta.__call__')
instance = super().__call__(*args, **kwargs)
print('leaving MetaMeta.__call__')
return instance
class Meta(type, metaclass=MetaMeta):
print('parsing Meta')
def __init__(self, *args, **kwargs):
print(' entering Meta.__init__')
super().__init__(*args, **kwargs)
print(' leaving Meta.__init__')
def __new__(cls, *args, **kwargs):
print(f' entering Meta.__new__')
instance = super().__new__(cls, *args, **kwargs)
print(' leaving Meta.__new__')
return instance
class Parent(object, metaclass=Meta):
print('parsing Parent')
def __init_subclass__(cls, *args, **kwargs):
print(' entering Parent.__init_subclass__')
super().__init_subclass__(*args, **kwargs)
print(' leaving Parent.__init_subclass__')
class Child(Parent):
print('parsing Child')
Which results in:
parsing MetaMeta
parsing Meta
parsing Parent
entering MetaMeta.__call__
entering Meta.__new__
leaving Meta.__new__
entering Meta.__init__
leaving Meta.__init__
leaving MetaMeta.__call__
parsing Child
entering MetaMeta.__call__
entering Meta.__new__
entering Parent.__init_subclass__
leaving Parent.__init_subclass__
leaving Meta.__new__
entering Meta.__init__
leaving Meta.__init__
leaving MetaMeta.__call__
A metaclass can still be setting up the class in Meta.__new__
after __init_subclass__
is called. Which seems odd to me. Why is that the case, and is there any way to provide code in Parent
(without a custom metaclass) that is run completely after Meta.__new__
(and probably before Meta.__init__
)?
Or am I missing something completely?
FYI, I found some related topics, but not quite what I was looking for:
- The call order of python3 metaclass
- Arguments of __new__ and __init__ for metaclasses
- https://docs.python.org/3/reference/datamodel.html#customizing-class-creation
Perhaps a more concise way to ask this question is "why does Python (v3.9 at least) have Meta.__new__
invoke Parent.__init_subclass__
, instead of having MetaMeta.__call__
invoke it immediately after __new__
is complete?
Note that after asking, I did find some python.org discussion around this topic, but I don't think they clarify why: