On Wed, 30 Dec 2020 at 04:38, Ethan Furman <et...@stoneleaf.us> wrote: > > 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 | ncogh...@gmail.com | Brisbane, Australia _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-le...@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/QWVL2UPV2MCJJXTO3TOANH5YSR2GEDBP/ Code of Conduct: http://python.org/psf/codeofconduct/