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/

Reply via email to