classification
Title: Able to instantiate a subclass with abstract methods from __init_subclass__ of the ABC
Type: behavior Stage:
Components: Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: BTaskaya, ethan.furman, jbasko, rhettinger, stutzbach, tda
Priority: normal Keywords:

Created on 2019-01-24 07:57 by jbasko, last changed 2021-02-01 20:10 by ethan.furman.

Messages (5)
msg334284 - (view) Author: Jazeps Basko (jbasko) Date: 2019-01-24 07:57
I am creating and registering singleton instances of subclasses of ABC in the ABC's __init_subclass__ and I just noticed that I am able to instantiate even the classes which have abstract methods.



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


Actual Output:

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

Expected Output:

TypeError: Can't instantiate abstract class Derived with abstract methods do_something
msg365439 - (view) Author: Batuhan Taskaya (BTaskaya) * (Python committer) Date: 2020-03-31 23:57
__init_subclass__ is called way before (in the type_new, when type is in the process of getting created) the object's __new__ (object_new) (which raises that TypeError).
msg383700 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-12-24 19:06
I tried update `abc.py` with the same dance I have to use with `Enum`:

    def __new__(mcls, name, bases, namespace, **kwargs):
        # remove current __init_subclass__ so previous one can be found with getattr
        try:
            new_init_subclass = namespace.get('__init_subclass__')
            del namespace['__init_subclass__']
        except KeyError:
            pass
        # create our new ABC type
        if bases:
            bases = (_NoInitSubclass, ) + bases
            abc_cls = super().__new__(mcls, name, bases, namespace, **kwargs)
            abc_cls.__bases__ = abc_cls.__bases__[1:]
        else:
            abc_cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        old_init_subclass = getattr(abc_cls, '__init_subclass__', None)
        # restore new __init_subclass__ (if there was one)
        if new_init_subclass is not None:
            abc_cls.__init_subclass__ = classmethod(new_init_subclass)
        _abc_init(abc_cls)
        # call parents' __init_subclass__
        if old_init_subclass is not None:
            old_init_subclass(**kwargs)
        return abc_cls

But I get this error:

Fatal Python error: init_import_site: Failed to import the site module
Python runtime state: initialized
Traceback (most recent call last):
  File "/home/ethan/source/python/cpython/Lib/site.py", line 73, in <module>
    import os
  File "/home/ethan/source/python/cpython/Lib/os.py", line 29, in <module>
    from _collections_abc import _check_methods
  File "/home/ethan/source/python/cpython/Lib/_collections_abc.py", line 122, in <module>
    class Coroutine(Awaitable):
  File "/home/ethan/source/python/cpython/Lib/abc.py", line 103, in __new__
    abc_cls.__bases__ = abc_cls.__bases__[1:]
TypeError: __bases__ assignment: 'Awaitable' object layout differs from '_NoInitSubclass'
msg383947 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-12-29 03:08
If the patch in issue42775 is committed, this problem will be solved.
msg386101 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-02-01 20:10
That patch was rejected in favor of updating Enum to use `__set_name__` to do the final creation of enum members.

The same thing could be done for ABC, but I lack the C skills to make it happen.
History
Date User Action Args
2021-02-01 20:10:34ethan.furmansetmessages: + msg386101
2021-02-01 20:10:16ethan.furmansetmessages: - msg386099
2021-02-01 20:09:42ethan.furmansetassignee: ethan.furman ->
superseder: __init_subclass__ should be called in __init__ ->
messages: + msg386099
versions: + Python 3.10, - Python 3.7, Python 3.8
2020-12-29 03:08:11ethan.furmansetassignee: ethan.furman
superseder: __init_subclass__ should be called in __init__
messages: + msg383947
2020-12-24 19:06:22ethan.furmansetmessages: + msg383700
2020-12-24 03:31:47ethan.furmansetnosy: + ethan.furman
2020-09-09 20:14:56tdasetnosy: + tda

versions: + Python 3.8
2020-03-31 23:57:41BTaskayasetmessages: + msg365439
2020-03-31 23:42:17BTaskayasetmessages: - msg334467
2019-01-28 13:54:50BTaskayasetnosy: + BTaskaya
messages: + msg334467
2019-01-25 18:26:55xtreaksetnosy: + rhettinger, stutzbach
2019-01-24 07:57:05jbaskocreate