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/

Reply via email to