This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

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

Created on 2019-01-24 07:57 by jbasko, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (12)
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.
msg397142 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-07-08 13:51
Ethan: as far as I understand, there's no actual problem here. The report adds a print statement in __init_subclass__ that says that instance was created, while in fact instance is not created. I can confirm what Batuhan said, in my testing, that it fails with TypeError as expected:

python3 ~/temp/a.py                                                                                                                                                                                                         ----VICMD----
Created instance of <class '__main__.Derived'> easily: <__main__.Derived object at 0x106fbb9d0>
Traceback (most recent call last):
  File "/Users/ak/temp/a.py", line 54, in <module>
    Derived()
TypeError: Can't instantiate abstract class Derived with abstract method do_something


I think this can be closed as not a bug.
msg397173 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-07-09 00:24
Andrei, which code did you try?  I'm still seeing the failure (i.e. the class being created) in 3.8 and forward.
msg397174 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-07-09 00:30
Ethan, here  is the code I tried with 3.9, and the failure:

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
Derived()

Output:
python3 ~/temp/a.py                                                                                                                                                        ----VICMD----
Traceback (most recent call last):
  File "/Users/ak/temp/a.py", line 53, in <module>
    Derived()
TypeError: Can't instantiate abstract class Derived with abstract method do_something
msg397175 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-07-09 00:47
I missed the fact that instance was being indeed created, I can see it now.
msg397176 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-07-09 00:53
Oops, I see the issue now. I was playing around with the code sample and  had the print line commented out inadvertently when I made the output I pasted above. Sorry for the noise!
msg397177 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-07-09 01:18
At worst, this seems like only a minor nuisance.  The ABC metaclass is limited in its powers and seems to reasonably cover the common use cases.  
I recommend leaving it as is.  Guido, what do you think?
msg397182 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-07-09 02:42
> At worst, this seems like only a minor nuisance.  The ABC metaclass is limited in its powers and seems to reasonably cover the common use cases.  
> I recommend leaving it as is.  Guido, what do you think?

Agreed. The abstractness checks are limited and not intended to prevent all ways of creating abstract instances -- just to catch typical mistakes early. (I vaguely recall another bug report about a weakness in this check that I resolved in the same way.)
History
Date User Action Args
2022-04-11 14:59:10adminsetgithub: 79996
2021-07-09 02:42:08gvanrossumsetstatus: open -> closed
resolution: not a bug
messages: + msg397182

stage: resolved
2021-07-09 01:18:00rhettingersetassignee: gvanrossum

messages: + msg397177
nosy: + gvanrossum
2021-07-09 00:53:38andrei.avksetmessages: + msg397176
2021-07-09 00:47:44andrei.avksetmessages: + msg397175
2021-07-09 00:30:11andrei.avksetmessages: + msg397174
2021-07-09 00:24:48ethan.furmansetmessages: + msg397173
versions: + Python 3.8, Python 3.9, Python 3.11
2021-07-08 13:51:33andrei.avksetnosy: + andrei.avk
messages: + msg397142
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 -> (no value)
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