5.1. Abstract Base Class
Since Python 3.0: PEP 3119 -- Introducing Abstract Base Classes
Cannot instantiate
Possible to indicate which method must be implemented by child
Inheriting class must implement all methods
Some methods can have implementation
Python Abstract Base Classes [1]
- abstract class
Class which can only be inherited, not instantiated. Abstract classes can have regular methods which will be inherited normally. Some methods can be marked as abstract, and those has to be overwritten in subclasses.
- abstract method
Method which has to be present (implemented) in a subclass.
- abstract static method
Static method which must be implemented in a subclass.
- abstract property
Class variable which has to be present in a subclass.
5.1.1. SetUp
>>> from abc import ABC, ABCMeta, abstractmethod, abstractproperty
5.1.2. Syntax
Inherit from
ABC
At least one method must be
abstractmethod
orabstractproperty
Body of the method is not important, it could be
raise NotImplementedError
orpass
>>> class Account(ABC):
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
You cannot create instance of a class Account
as of
this is the abstract class:
>>> mark = Account()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Account without an implementation for abstract method 'login'
5.1.3. Implement Abstract Methods
All abstract methods must be covered
Abstract base class can have regular (not abstract) methods
Regular methods will be inherited as normal
Regular methods does not need to be overwritten
Abstract base class:
>>> class Account(ABC):
... def __init__(self, username: str, password: str) -> None:
... self.username = username
... self.password = password
...
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
...
... @abstractmethod
... def logout(self) -> None:
... raise NotImplementedError
Implementation:
>>> class User(Account):
... def login(self) -> None:
... print('Logging-in')
...
... def logout(self) -> None:
... print('Logging-out')
Use:
>>> mark = User(username='mwatney', password='Ares3')
>>>
>>> mark.login()
Logging-in
>>>
>>> mark.logout()
Logging-out
Mind, that all abstract methods must be covered, otherwise it will raise an error. Regular methods (non-abstract) will be inherited as normal and they does not need to be overwritten in an implementing class.
5.1.4. ABCMeta
Uses
metaclass=ABCMeta
Not recommended since Python 3.4
Use inheriting
ABC
instead
There is also an alternative (older) way of defining abstract base classes.
It uses metaclass=ABCMeta
specification during class creation.
This method is not recommended since Python 3.4 when ABC
class was
introduce to simplify the process.
>>> class Account(metaclass=ABCMeta):
... def __init__(self, username: str, password: str) -> None:
... self.username = username
... self.password = password
...
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
5.1.5. Abstract Property
abc.abstractproperty
is deprecated since Python 3.3Use
property
withabc.abstractmethod
instead
>>> class Account(ABC):
... @abstractproperty
... def AGE_MIN(self) -> int:
... raise NotImplementedError
...
... @abstractproperty
... def AGE_MAX(self) -> int:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... AGE_MIN: int = 18
... AGE_MAX: int = 65
Since 3.3 instead of @abstractproperty
using both @property
and @abstractmethod
is recommended.
>>> class Account(ABC):
... @property
... @abstractmethod
... def AGE_MIN(self) -> int:
... raise NotImplementedError
...
... @property
... @abstractmethod
... def AGE_MAX(self) -> int:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... AGE_MIN: int = 18
... AGE_MAX: int = 65
Mind that the order here is important and it cannot be the other way around.
Decorator closest to the method must be @abstractmethod
and then
@property
at the most outer level. This is because @abstractmethod
sets special attribute on the method and then this method with attribute
is turned to the property. This does not work if you reverse the order.
5.1.6. Problem: Base Class Has No Abstract Method
In order to use Abstract Base Class you must create at least one abstract method. Otherwise it won't prevent from instantiating:
>>> class Account(ABC):
... pass
>>>
>>>
>>> mark = Account()
>>> mark
<__main__.Account object at 0x...>
The code above will allo to create mark
from Account
because
this class did not have any abstract methods.
5.1.7. Problem: Base Class Does Not Inherit From ABC
In order to use Abstract Base Class you must inherit from ABC
in your
base class. Otherwise it won't prevent from instantiating:
>>> class Account:
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... pass
>>>
>>>
>>> mark = User()
>>> mark
<__main__.User object at 0x...>
This code above will allow to create mark
from User
because
Account
class does not inherit from ABC
.
5.1.8. Problem: All Abstract Methods are not Implemented
Must implement all abstract methods:
>>> class Account(ABC):
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
...
... @abstractmethod
... def logout(self) -> None:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... pass
>>>
>>>
>>> mark = User()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class User without an implementation for abstract methods 'login', 'logout'
The code above will prevent from creating User
instance,
because class User
does not overwrite all abstract methods.
In fact it does not overwrite any abstract method at all.
5.1.9. Problem: Some Abstract Methods are not Implemented
All abstract methods must be implemented in child class:
>>> class Account(ABC):
... @abstractmethod
... def login(self) -> None:
... raise NotImplementedError
...
... @abstractmethod
... def logout(self) -> None:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... def login(self) -> None:
... print('Logging-in')
>>>
>>>
>>> mark = User()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class User without an implementation for abstract method 'logout'
The code above will prevent from creating User
instance, because class
User
does not overwrite all abstract methods. The .login()
method
is not overwritten. In order abstract class to work, all methods must be
covered.
5.1.10. Problem: Child Class has no Abstract Property
Using
abstractproperty
>>> class Account(ABC):
... @abstractproperty
... def AGE_MIN(self) -> int:
... raise NotImplementedError
...
... @abstractproperty
... def AGE_MAX(self) -> int:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... AGE_MIN: int = 18
>>>
>>>
>>> mark = User()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class User without an implementation for abstract method 'AGE_MAX'
The code above will prevent from creating User
instance, because class
User
does not overwrite all abstract properties. The AGE_MAX
is
not covered.
5.1.11. Problem: Child Class has no Abstract Properties
Using
property
andabstractmethod
>>> class Account(ABC):
... @property
... @abstractmethod
... def AGE_MIN(self) -> int:
... raise NotImplementedError
...
... @property
... @abstractmethod
... def AGE_MAX(self) -> int:
... raise NotImplementedError
>>>
>>>
>>> class User(Account):
... AGE_MIN: int = 18
>>>
>>>
>>> mark = User()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class User without an implementation for abstract method 'AGE_MAX'
The code above will prevent from creating User
instance, because class
User
does not overwrite all abstract properties. The AGE_MAX
is
not covered.
5.1.12. Problem: Invalid Order of Decorators
Invalid order of decorators:
@property
and@abstractmethod
Should be: first
@property
then@abstractmethod
>>> class Account(ABC):
... @abstractmethod
... @property
... def AGE_MIN(self) -> int:
... raise NotImplementedError
...
... @abstractmethod
... @property
... def AGE_MAX(self) -> int:
... raise NotImplementedError
...
Traceback (most recent call last):
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable
Note, that this will not even allow to define User
class at all.
5.1.13. Problem: Overwrite ABC File
abc
is common name and it is very easy to create file, variable
or module with the same name as the library, hence overwriting it.
In case of error check all entries in sys.path
or sys.modules['abc']
to find what is overwriting it:
>>> from pprint import pprint
>>> import sys
>>> sys.modules['abc']
<module 'abc' (frozen)>
>>> pprint(sys.path)
['/Users/watney/myproject',
'/Applications/PyCharm 2024.3.app/Contents/plugins/python/helpers/pydev',
'/Applications/PyCharm 2024.3.app/Contents/plugins/python/helpers/pycharm_display',
'/Applications/PyCharm 2024.3.app/Contents/plugins/python/helpers/third_party/thriftpy',
'/Applications/PyCharm 2024.3.app/Contents/plugins/python/helpers/pydev',
'/Applications/PyCharm 2024.3.app/Contents/plugins/python/helpers/pycharm_matplotlib_backend',
'/usr/local/Cellar/python@3.13/3.13.0/Frameworks/Python.framework/Versions/3.13/lib/python313.zip',
'/usr/local/Cellar/python@3.13/3.13.0/Frameworks/Python.framework/Versions/3.13/lib/python3.13',
'/usr/local/Cellar/python@3.13/3.13.0/Frameworks/Python.framework/Versions/3.13/lib/python3.13/lib-dynload',
'/Users/watney/myproject/venv-3.13/lib/python3.13/site-packages']
5.1.14. Use Case - 1
Abstract Class:
>>> from abc import ABC, abstractmethod
>>> class Document(ABC):
... def __init__(self, filename):
... self.filename = filename
...
... @abstractmethod
... def display(self):
... pass
>>>
>>>
>>> class PDFDocument(Document):
... def display(self):
... print('Display file content as PDF Document')
>>>
>>> class WordDocument(Document):
... def display(self):
... print('Display file content as Word Document')
>>> file = PDFDocument('myfile.pdf')
>>> file.display()
Display file content as PDF Document
>>> file = WordDocument('myfile.pdf')
>>> file.display()
Display file content as Word Document
>>> file = Document('myfile.txt')
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Document without an implementation for abstract method 'display'
5.1.15. Use Case - 2
>>> from abc import ABC, abstractmethod
>>> class Element(ABC):
... def __init__(self, name):
... self.name = name
...
... @abstractmethod
... def render(self):
... pass
>>>
>>>
>>> def render(component: list[Element]):
... for element in component:
... element.render()
>>> class TextInput(Element):
... def render(self):
... print(f'Rendering {self.name} TextInput')
>>>
>>>
>>> class Button(Element):
... def render(self):
... print(f'Rendering {self.name} Button')
>>> login_window = [
... TextInput(name='Username'),
... TextInput(name='Password'),
... Button(name='Submit'),
... ]
>>>
>>> render(login_window)
Rendering Username TextInput
Rendering Password TextInput
Rendering Submit Button
5.1.16. Use Case - 3
>>> from abc import ABC, abstractmethod, abstractproperty
>>>
>>> class Account(ABC):
... age: int
...
... @property
... @abstractmethod
... def AGE_MAX(self) -> int: ...
...
... @abstractproperty
... def AGE_MIN(self) -> int: ...
...
... def __init__(self, age):
... if not self.AGE_MIN <= age < self.AGE_MAX:
... raise ValueError('Age is out of bounds')
... self.age = age
>>> class User(Account):
... AGE_MIN = 30
... AGE_MAX = 50
>>> mark = User(age=42)
5.1.17. Use Case - 4
>>> from abc import ABC, abstractmethod
>>> from datetime import timedelta
>>>
>>>
>>> class Cache(ABC):
... @property
... @abstractmethod
... def timeout(self) -> timedelta:
... raise NotImplementedError
...
... @abstractmethod
... def set(self, key: str, value: str) -> None:
... raise NotImplementedError
...
... @abstractmethod
... def get(self, key: str) -> str:
... raise NotImplementedError
...
... @abstractmethod
... def delete(self, key: str) -> None:
... raise NotImplementedError
>>>
>>>
>>> class DatabaseCache(Cache):
... timeout = timedelta(minutes=10)
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def delete(self, key: str) -> None: ...
>>>
>>> class FilesystemCache(Cache):
... timeout = timedelta(minutes=10)
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def delete(self, key: str) -> None: ...
>>>
>>> class MemoryCache(Cache):
... timeout = timedelta(minutes=10)
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def delete(self, key: str) -> None: ...
>>>
>>>
>>> cache: Cache = MemoryCache()
>>> cache.set('firstname', 'Mark')
>>> cache.set('lastname', 'Watney')
>>> cache.get('firstname')
>>> cache.get('lastname')
>>> cache.delete('firstname')
>>> cache.delete('lastname')
5.1.18. Further Reading
5.1.19. References
5.1.20. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AbstractClass Syntax
# - Difficulty: easy
# - Lines: 8
# - Minutes: 3
# %% English
# 1. Create abstract class `Account`
# 2. Define abstract methods `login()` and `logout()`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Stwórz klasę abstrakcyjną `Account`
# 2. Zdefiniuj metody abstrakcyjne `login()` i `logout()`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isclass, isabstract, ismethod
>>> assert isclass(Account)
>>> assert isabstract(Account)
>>> assert hasattr(Account, 'login')
>>> assert hasattr(Account, 'logout')
>>> assert hasattr(Account.login, '__isabstractmethod__')
>>> assert hasattr(Account.logout, '__isabstractmethod__')
>>> assert Account.login.__isabstractmethod__ == True
>>> assert Account.logout.__isabstractmethod__ == True
>>> result = Account()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Account without an implementation for abstract methods 'login', 'logout'
"""
# Define abstract class `Account`
# With abstract methods `login()` and `logout()`
class Account:
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AbstractClass Implementation
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3
# %% English
# 1. Define class `User` inheriting from `Account`
# 2. Overwrite all abstract methods, leave `pass` as content
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `User` dziedziczące po `Account`
# 2. Nadpisz wszystkie metody abstrakcyjne, pozostaw `pass` jako treść
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isclass, isabstract, ismethod
>>> assert isclass(Account)
>>> assert isclass(User)
>>> assert isabstract(Account)
>>> assert not isabstract(User)
>>> assert hasattr(Account, 'login')
>>> assert hasattr(Account, 'logout')
>>> assert hasattr(User, 'login')
>>> assert hasattr(User, 'logout')
>>> assert not hasattr(User.login, '__isabstractmethod__')
>>> assert not hasattr(User.logout, '__isabstractmethod__')
>>> assert hasattr(Account.login, '__isabstractmethod__')
>>> assert hasattr(Account.logout, '__isabstractmethod__')
>>> assert Account.login.__isabstractmethod__ == True
>>> assert Account.logout.__isabstractmethod__ == True
>>> result = Account()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Account without an implementation for abstract methods 'login', 'logout'
>>> result = User()
>>> assert ismethod(result.login)
"""
from abc import ABC, abstractmethod
class Account(ABC):
@abstractmethod
def login(self):
pass
@abstractmethod
def logout(self):
pass
# Define class `User` inheriting from `Account`
# Overwrite all abstract methods, leave `pass` as content
class User:
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AbstractClass Implement
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3
# %% English
# 1. Define class `User` implementing `Account`
# 2. Overwrite all abstract methods, leave `pass` as content
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `User` implementującą `Account`
# 2. Nadpisz wszystkie metody abstrakcyjne, pozostaw `pass` jako treść
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isabstract, isclass, ismethod, signature
>>> assert isclass(Account)
>>> assert isabstract(Account)
>>> assert hasattr(Account, '__init__')
>>> assert hasattr(Account, 'login')
>>> assert hasattr(Account, 'logout')
>>> assert hasattr(Account.login, '__isabstractmethod__')
>>> assert hasattr(Account.logout, '__isabstractmethod__')
>>> assert Account.login.__isabstractmethod__ == True
>>> assert Account.logout.__isabstractmethod__ == True
>>> Account.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>}
>>> Account.__init__.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>, 'return': None}
>>> Account.login.__annotations__
{'username': <class 'str'>, 'password': <class 'str'>, 'return': None}
>>> Account.logout.__annotations__
{'return': None}
>>> assert isclass(User)
>>> result = User(firstname='Mark', lastname='Watney')
>>> result.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>}
>>> assert hasattr(result, '__init__')
>>> assert hasattr(result, 'logout')
>>> assert hasattr(result, 'login')
>>> assert ismethod(result.__init__)
>>> assert ismethod(result.logout)
>>> assert ismethod(result.login)
>>> signature(result.__init__) # doctest: +NORMALIZE_WHITESPACE
<Signature (firstname: str, lastname: str) -> None>
>>> signature(result.login)
<Signature (username: str, password: str) -> None>
>>> signature(result.logout)
<Signature () -> None>
>>> assert result.login('mwatney', 'Ares3') is None, 'Do not implement login() method'
>>> assert result.logout() is None, 'Do not implement logout() method'
"""
from abc import ABC, abstractmethod
class Account(ABC):
firstname: str
lastname: str
def __init__(self, firstname: str, lastname: str) -> None:
self.firstname = firstname
self.lastname = lastname
@abstractmethod
def login(self, username: str, password: str) -> None:
...
@abstractmethod
def logout(self) -> None:
...
# Define class `User` implementing `Account`
# Overwrite all abstract methods, leave `pass` as content
class User:
pass