On 1/5/21 4:22 AM, Nick Coghlan wrote:
> On Wed, 30 Dec 2020 at 04:38, Ethan Furman wrote:
>> ... 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.
>
> 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.
And if that's not possible? (More on that later.)
> 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.
If the custom metaclass hasn't finished its work, I don't see how that is
helpful.
> 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
I have two concrete examples of custom metaclasses currently broken by calling
`__init_subclass__` from `type.__new__`:
- `ABCMeta`
- `Enum`
Perhaps `ABCMeta` can be easily fixed -- it is calling the function that records all abstract methods, etc., after the
`type.__new__` call; can it be called before?
`Enum` cannot be fixed: it must call `type.__new__` to get the new class object, and it must have the new class object
in order to create the members as instances of that `Enum` class -- which means that anyone trying to use
`__init_subclass__` to do anything with an `Enum`'s members is doomed to failure because those members don't exist yet.
Possible work-arounds for `Enum`:
- call `__init_subclass__` twice - once from `type.__new__`, and then again
by `EnumMeta` after the members have been added
* problem: authors of `__init_subclass__` have to be aware that they will
get called twice
- do horribly brittle mro rewrites in `EnumMeta` so the `type.__new__` call
is a no-op, then `EnumMeta` calls `__init_subclass__` when the members
have been created
* problem: horribly brittle
------------------------------------------------------------------------------
While I would prefer to see the calls to `__init_subclass__` and `__set_name__` (and any other such enhancements) moved
to `type.__init__`, another possibility would be to add a class attribute/keyword, `_pep487_to_init`, which if present
causes `type.__new__` to skip those calls and `type.__init__` to make them. Then the only headaches would be when
metaclasses with different desires were combined.
I am also open to other solutions that would allow `EnumMeta` and its users to be able to use `__init_subclass__` with a
fully created class.
--
~Ethan~
_______________________________________________
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/NIIQPQO3DLDTMV6PJQNGM3ODUQHJBYSQ/
Code of Conduct: http://python.org/psf/codeofconduct/