On Wed, 30 Dec 2020 at 04:38, Ethan Furman
No, we can't. There is a window where the subclass is initialized after `typing_new()` returned before `__init__` starts, and you propose to move the subclass after that window. There may be code that depends on the class being initialized at that point, and you will break that code.
True, there will be a few custom metaclasses that need to move some code from their `__new__` to `__init__` instead, and a few that need to add an `__init__` to consume any keyword arguments that don't need to get passed to `__init_subclass__`. That seems like a small price to pay to be able to write custom metaclasses that are able to fully participate in the `__set_name__` and `__init_subclass__` protocols.
From https://www.python.org/dev/peps/pep-0487/#implementation-details: """As a third option, all the work could have been done in type.__init__. Most metaclasses do their work in __new__, as this is recommended by the documentation. Many metaclasses modify their arguments before they pass them over to super().__new__. For compatibility with those kind of classes, the hooks should be called from __new__.""" That isn't the clearest way of saying it, but the intent was to make sure that metaclass __new__ implementations continued to see fully initialised class objects as soon as they returned from type.__new__(), with type.__init__() continuing to be essentially a no-op. If a metaclass wants to modify the attributes on the type in a way that is visible to __init_subclass__ and __set_name__, it needs to modify the namespace dict passed to type().__new__, not modify the type after it has already been created: ``` class Meta(type): # def __new__(mcls, name, bases, namespace, **kwds): # create new class, which will call __init_subclass__ and __set_name__ new_namespace = namespace.copy() # Customize class contents new_namespace["some_attr"] = 9 return type.__new__(mcls, name, bases, new_namespace, **kwds) ``` If you need to access the class being defined from __init_subclass__, it gets passed in, and if you need to access it from __set_name__, then it gets passed in there as well. If you need to access the class being defined from elsewhere, then the `__class__` cell used by zero-arg super is already populated before any of those hooks get called. As a CPython implementation detail, that cell is actually passed through to type().__new__ as `__classcell__` in the namespace. If that was elevated to a language feature rather than a CPython implementation detail, then metaclasses could write: cls_cell = namespace["__classcell__"] @property def defining_class(obj): return cls_cell.get() namespace[defining_class.fget.__name__] = defining_class Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia