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:
 
    