Introduction to Python Abstract Classes and Abstract Methods; Define and Implement Python Abstract Classes and Abstract Methods
Prerequisites
Reading this article presupposes a good grasp of Python class inheritance as well as class polymorphism, which you can check out Introduction to Python Class Inheritance; Implement Python Class Inheritance and Multiple Inheritance, and Override Methods for information on that.
Interfaces in Python
You can’t define interfaces for Python as you can in other languages (e.g., C#) by using a keyword like interface
; “interface” in Python is a conceptual term used to denote a functional implementation convention.
Define Python Abstract Classes
Although you can’t define an interface, Python abstract classes can achieve an approximation of an interface by constraining their derived classes to implement certain methods or attributes through overrides.
Similarly, Python does not provide a keyword like abstract
for declaring an abstract class or an abstract base class (ABC); the definition of a Python abstract base class needs to be accomplished by specifying a metaclass, and any class that specifies the ABCMeta
class of the abc
module as a metaclass can be called a Python abstract base class. Specifying a metaclass requires the keyword metaclass
, so the syntax for defining a Python abstract base class is as follows.
class <classname>(<metaclass=ABCMeta>):
<block>
- classname part
classname
is the name of the abstract base class, which needs to conform to Python's identifier specification and cannot use Python keywords or reserved keywords.- block part
block
is the body code of the abstract base class and needs to be indented with some sort of whitespace character to indicate that it belongs to the abstract base class.
The ABCMeta class of the Python abc module
The ABCMeta
class of the abc
module inherits from type
and can be used as a metaclass for other classes to realize the effects of abstract base classes. Python metaclasses are often used to exert more control over the creation of instances, and one of the uses of the ABCMeta
class is to check if all the abstract methods or attributes have been overridden, and if not, the instance cannot be created. Otherwise, ABCMeta
doesn’t affect the class much in other ways; you can do the usual things in Python abstract classes, such as defining and using non-abstract instance methods, class methods, and so on.
It is important to note that the ABCMeta
class does not appear in the Method Resolution Order of Python abstract base classes and their derived classes, and the ABCMeta
class is not a base class of Python abstract base classes.
Next, we specify ABCMeta
as a metaclass of class Movable
, and look at the Method Resolution Order and base classes of the abstract base class Movable
, and find that ABCMeta
is not one of them.
from abc import ABCMeta
# Abstract base class representing movable
class Movable(metaclass=ABCMeta):
pass
# Show Movable's Method Resolution Order and base classes
print(Movable.__mro__)
print(Movable.__bases__)
(<class '__main__.Movable'>, <class 'object'>)
(<class 'object'>,)
Of course, using the keyword metaclass
may feel odd, and Python provides an alternative way of defining an abstract base class, by inheriting directly from the ABC
class of the abc
module, with the following syntax form, where classname
and block
have the same meaning as before.
class <classname>(ABC):
<block>
The ABC class of the Python abc module
The ABC
class of the abc
module is a helper class that designates ABCMeta
as a metaclass, and Python classes derived from ABC
become abstract base classes. You can’t use ABC
via the metaclass
keyword as you would use the ABCMeta
class; that would cause some problems.
Unlike the ABCMeta
class, the ABC
class appears in the Method Resolution Order of Python’s abstract base classes and its derived classes, and the ABC
class is the base class of Python abstract base classes.
Next, we derive the Accessible
class from ABC
, look at the Method Resolution Order and base classes of the abstract base class Accessible
, and find ABC
in it.
from abc import ABC
# Abstract base class representing accessible
class Accessible(ABC):
pass
# Show Accessible's Method Resolution Order and base classes
print(Accessible.__mro__)
print(Accessible.__bases__)
(<class '__main__.Accessible'>, <class 'abc.ABC'>, <class 'object'>)
(<class 'abc.ABC'>,)
Define Abstract Methods for Python Abstract Classes
In Python abstract base classes or their derived classes, you can use the @abstractmethod
decorator provided by the abc
module to define abstract methods or abstract attributes that are essentially methods. The @abstractmethod
decorator can be used in conjunction with other decorators (@classmethod
, @staticmethod
, @property
, etc.) to form more complex method definitions, such as abstract class methods, abstract static methods, abstract properties, etc.
Python 3.2 used to include some other decorators on abstraction that were deprecated in Python 3.3, they are @abstractclassmethod
, @abstractstaticmethod
, @abstractproperty
.
ABCMeta
checks whether a Python class derived from an abstract base class overrides all defined abstract methods or attributes in the inheritance chain when an instance of that Python class is created.
The @abstractmethod decorator of the Python abc module
The main purpose of the @abstractmethod
decorator is to mark Python abstract methods in order to allow the ABCMeta
class to inspect them; it does not affect other aspects of the marked method. For example, methods marked as abstract methods can have their own body code and can be accessed in derived classes via super
.
The @abstractmethod
decorator should be written after related decorators (@classmethod
, @staticmethod
, @property
, etc.) to avoid raising the exception AttributeError
.
Python abstract class methods and abstract static methods can be called in an unimplemented state
Since ABCMeta
checks for implementations of Python abstract methods or attributes only when creating an instance, Python abstract class methods and abstract static methods, or abstract class attributes that are essentially methods, can be called directly without being implemented, as long as this process does not create an associated Python instance.
In the following code, the Animal
class defines two abstract methods, count
and name
, which are not implemented by the derived class Cat
, but are feasible to be called via Cat
or Animal
.
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
# Abstract class method count
@classmethod
@abstractmethod
def count(cls):
pass
# Abstract static method name
@staticmethod
@abstractmethod
def name():
print('What\'s the name?')
class Cat(Animal):
pass
# Calls the unimplemented static method name and the unimplemented class method count
Cat.name()
Animal.count()
# ERROR Cat does not implement the abstract base class Animal
Cat().name()
What's the name?
…
TypeError: Can't instantiate abstract class Cat without an implementation for abstract methods 'count', 'name'
Update Python abstract classes with the update_abstractmethods function
The abc
module’s update_abstractmethods
function (which supports use as a class decorator) can be used to update a Python abstract class when abstract attributes are dynamically added to the class, or when the state of an abstract attribute changes.
It should be noted that the update_abstractmethods
function only updates the target abstract class, it does not update the base classes or derived classes of the target abstract class.
update_abstractmethods(cls)
- cls parameter
The
cls
parameter is the abstract class to be updated.
Next, we dynamically add an abstract method show
to the abstract base class Color
, which is not implemented by the Black
class, but the creation of its instances is feasible because ABCMeta
is not aware of the newly added abstract method until the related abstract class is updated via the update_abstractmethods
function.
from abc import ABC, abstractmethod, update_abstractmethods
# Define the abstract base class Color and the derived class Black
class Color(ABC):
pass
class Black(Color):
pass
# Dynamically add an abstract method to the abstract base class
@abstractmethod
def show(self):
pass
Color.show = show
print('Create the first Black instance')
# There is no problem with creating an instance
Black()
# Update the abstract classes Color and Black
update_abstractmethods(Color)
update_abstractmethods(Black)
print('Create the second Black instance')
# ERROR You can't create an instance because Black doesn't implement the abstract method show
Black()
Create the first Black instance
Create the second Black instance
…
TypeError: Can't instantiate abstract class Black without an implementation for abstract method 'show'
Implement Python Abstract Classes and Abstract Methods
As described above, overriding an already defined Python abstract method or an abstract attribute that is essentially a method means that the abstract method or attribute is implemented. A Python abstract class is said to be implemented when all of its abstract methods and attributes are implemented.
Overriding Python abstract methods or attributes cannot be done with the decorator @abstractmethod
When overriding a Python abstract method or attribute, you must not write the decorator @abstractmethod
for it again, or the method or attribute will be recognized as an abstract definition rather than an implementation of the abstraction.
In the following example, the Flower
class implements the abstract method grow
of the Plant
class and defines its own abstract class method count
, so neither Plant
nor Flower
can create instances, and the BigFlower
class implements the abstract class method count
of the Flower
class, so BigFlower
can create instances.
from abc import ABCMeta, abstractmethod
class Plant(metaclass=ABCMeta):
# Define the abstract method grow
@abstractmethod
def grow(self):
print('Plant: Grow it!')
class Flower(Plant):
# Override the abstract method grow to implement it
@abstractmethod
def grow(self):
super().grow()
# Define the abstract class method count
@classmethod
@abstractmethod
def count(self):
pass
class BigFlower(Flower):
# Override the abstract method count to implement it
@classmethod
def count(self):
print('How many are there?')
# Cannot create instances of Plant and Flower
BigFlower().grow()
Plant: Grow it!
Register Python Classes as Virtual Derived Classes that Don't Need to Implement Abstract Classes
Any Python abstract base class, or one of its derived classes, has a method named register
that can be used to register an unrelated class as a virtual derived class of a Python abstract class. Unlike Python classes derived directly from an abstract class, a virtual derived class can be instantiated regardless of whether or not it already implements all of the abstract methods in the abstract class.
register(subclass)
- subclass parameter
The
subclass
parameter is the class to be registered as a virtual derived class.
In addition, in Python 3.3 or later, the register
method can be used as a class decorator to register a class as a virtual derived class of some Python abstract class. The syntax is @abstractclass.register
, with abstractclass
being an abstract class.
Python abstract classes do not appear in the Method Resolution Order of their virtual derived classes
Although a virtual derived class can be determined by the issubclass
function to be a derived class of a Python abstract class, and an instance of a virtual derived class can be determined by the isinstance
function to be an instance of a Python abstract class, a Python abstract class, and all of its abstract base classes, will not appear in the Method Resolution Order of its virtual derived classes, which means that you cannot use super
to access methods or other attributes in Python abstract classes because, well, these abstract classes are not searched by Python.
How can I view the cache tokens for Python's virtual derived classes?
Whenever the register
method of any Python abstract class is called, including when it is called as a decorator, the cache token of the Python virtual derived class is changed, which can be verified with the get_cache_token
function of the abc
module (requires Python 3.4 or later).
Next, we register Jerry
, Tom
as virtual derived classes of the abstract base class Man
via the register
method and use the function get_cache_token
to see the changes in the cache token.
After registration as virtual derived classes, Jerry
and Tom
can create instances with or without the implementation of abstract methods. However, since Man
is not in the Method Resolution Order of Jerry
and Tom
, the statement super().run()
of the Tom
class will result in the exception AttributeError
.
from abc import ABC, abstractmethod, get_cache_token
# Define the abstract base class Man
class Man(ABC):
@abstractmethod
def run(self):
pass
# Register the classes Jerry and Tom as virtual derived classes and see how the token changes
print(f'The token is {get_cache_token()}')
@Man.register
class Jerry():
pass
print(f'The token is {get_cache_token()}')
class Tom():
def run(self):
# ERROR Man is not in the MRO
super().run()
Man.register(Tom)
print(f'The token is {get_cache_token()}')
# Jerry and Tom are considered derived classes
print(issubclass(Jerry, Man))
print(issubclass(Tom, Man))
# You can create an instance as normal
print(isinstance(Jerry(), Man))
print(isinstance(Tom(), Man))
Tom().run()
The token is 24
The token is 25
The token is 26
True
True
True
True
…
AttributeError: 'super' object has no attribute 'run'
Use the class method __subclasshook__ to determine the derived class of a Python abstract class
You can define the class method __subclasshook__
for Python abstract classes to determine whether a class is a derived class of a Python abstract class. The __subclasshook__
method affects the issubclass
function, when its return value is True
or False
, the issubclass
function will also return True
or False
, and when its return value is NotImplemented
, the issubclass
function will determine and return the result according to its original logic.
Once the class method __subclasshook__
is defined for a Python abstract class, then __subclasshook__
will apply to all derived classes of that abstract class if it is not overridden by a derived class.
__subclasshook__(subclass)
- subclass parameter
The
subclass
parameter is the class that needs to be determined if it is a derived class.
We define the class method __subclasshook__
for the following Unit
class and return False
, which will cause all classes not to be recognized as derived classes of Unit
, even if they do inherit from Unit
.
from abc import ABC
class Unit(ABC):
# None of the classes are derived from the Unit
@classmethod
def __subclasshook__(cls, subclass):
print(f'Is {subclass} a derived class of Unit?')
return False
class Player(Unit):
pass
# View the relationship between Player and Unit
print(issubclass(Player, Unit))
print(isinstance(Player(), Unit))
Is <class '__main__.Player'> a derived class of Unit?
False
False