Last active
March 4, 2025 17:44
-
-
Save nottrobin/337dea07586ed0ecb5fe5d89cb629ebf to your computer and use it in GitHub Desktop.
A proper abstract base class that blocks subclassing its children
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from abc import ABC | |
class AbstractBase(ABC): | |
""" | |
Base class for creating abstract classes which resist instantiation. | |
Because Python's "abstract classes" derived from ABC do not natively prevent | |
instantiation, even though that's usually considered central to the definition | |
of an abstract class. | |
Subclasses of AbstractBase will raise a TypeError if instantiated directly: | |
base_instance = AbstractBase() # raises TypeError | |
Direct subclasses of AbstractBase will by abstract, and so also raise a TypeError | |
if instantiated directly: | |
class MyAbstractClass(AbstractBase): | |
pass | |
abstract_instance = MyAbstractClass() # raises TypeError | |
Children of abstract classes will be concrete unless declared abstract: | |
class MyConcreteChild(MyAbstractClass): | |
pass | |
class MyAbstractChild(MyAbstractClass, abstract=True) | |
pass | |
concrete_instance = MyConcreteChild() # Success | |
abstractchild_instance = MyAbstractChild() # raises TypeError | |
Based on @Aran-Fey's answer at https://stackoverflow.com/a/50100282/613540 | |
""" | |
_abstract: bool = True | |
def __init_subclass__(cls, /, abstract=False, **kwargs): | |
""" | |
Grandchildren of AbstractBase will default to having their "abstract" | |
property set to False. This can be overridden with `abstract=True`: | |
class MyAbstractClass(AbstractBase): | |
pass | |
class MyAbstractChild(MyAbstractClass, abstract=True): | |
pass | |
""" | |
parent = cls.mro()[1] | |
if parent is not AbstractBase: | |
cls._abstract = abstract | |
return super().__init_subclass__(**kwargs) | |
def __new__(cls, *args, **kwargs): | |
""" | |
When creating an instance, check the "abstract" property, and raise a | |
TypeError if it's True | |
""" | |
if cls._abstract: | |
raise TypeError(( | |
"Abstract classes descended from AbstractBase" | |
" may not be directly instantiated." | |
)) | |
return super().__new__(cls, *args, **kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment