On 12/28/20 9:31 PM, Guido van Rossum wrote:

Let me see if I can unpack this.

I observe that `type.__new__() ` is really the C function `type_new()` in typeobject.c, and hence I will refer to it by that name.

I understand that `type_new()` is the only way to create type objects, and it 
includes a call to `__init_subclass__()`.

In the source code of `type_new()`, calling `__init_subclass__()` is the last thing it does before returning the newly created class object, so at this point the class object is complete, *except* that any updates made by the caller of `type_new()` after `type_new()` returns have not been made, of course. (For example, `new_class.some_attr = 9` from Ethan's post, or `__abstractmethods__`, which is set by update_abstractmethods() in abc.py.)

This is really the heart of the issue. A major reason to write a custom metaclass is to be able to modify the returned class before giving it back to the user -- so even though `type_new` is complete the new class could easily not be complete, and calling `__init_subclass__` and `__set_name__` from `type_new` is premature.

Now here's something that Ethan said that I don't follow:

For Enum, this means that `__init_subclass__` doesn't have access to the new 
Enum's members (they haven't been added yet)

I would presume that in an example like the following, the members *are* set by the time `type_new()` is called. What am I missing?
```
class Color(enum.Enum):
     RED = 1
     GREEN = 2
     BLUE = 4
```
Maybe the problem is that the members are still set to their "naive" initial values (1, 2, 4) rather than to the corresponding enum values (e.g. `<Color.Red: 1>`)?

Before `type_new()` is called all the (future) members are removed from `namespace`, so they are not present when `__init_subclass__` is called. Even if they were left in as `{'RED': 1, `GREEN`: 2, `BLUE`: 4}` it would not be possible for an `__init_subclass__` to customize the members further, or record them in custom data structures, or run validation code on them, or etc.

Without a more elaborate use case I can't give that more than a shrug. This is how the `__init_subclass__()` protocol is designed.

The `__init_subclass__` and `__set_name__` protocols are intended to be run before a new type is finished, but creating a new type has three major steps:

- `__prepare__` to get the namespace
- `__new__` to get the memory and data structures
- `__init__` for any final polishing

We can easily move the calls from `type_new()` to `type_init`.

Note that for ABC, if you add or change the abstraction status of some class attributes, you can just call `update_abstractmethods()` and it will update `__abstractmethods__` based on the new contents of the class. This also sounds like no biggie to me.

The issue tracker sample code that fails (from #35815):

```
import abc

class Base(abc.ABC):
    #
    def __init_subclass__(cls, **kwargs):
        instance = cls()
        print(f"Created instance of {cls} easily: {instance}")
    #
    @abc.abstractmethod
    def do_something(self):
        pass

class Derived(Base):
    pass
```

And the output:

`Created instance of <class '__main__.Derived'> easily: <__main__.Derived object 
at 0x10a6dd6a0>`

If `Base` had been completed before the call to `__init_subclass__`, then `Derived` would have raised an error -- and it does raise an error with the patch I have submitted on Github.

--
~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/HG5SHTY76LKYQS7OY5CXH6TYMUGK7O5L/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to