Skip to content

Instantly share code, notes, and snippets.

@nottrobin
Last active March 4, 2025 17:44
Show Gist options
  • Save nottrobin/337dea07586ed0ecb5fe5d89cb629ebf to your computer and use it in GitHub Desktop.
Save nottrobin/337dea07586ed0ecb5fe5d89cb629ebf to your computer and use it in GitHub Desktop.
A proper abstract base class that blocks subclassing its children
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