Why Declare an Abstract Base Class?
To understand the need to declare a virtual subclass, we need to consider the example of a list-like object where you don’t want to put a restriction of only considering list or tuple. Before that let’s see how to use isinstance to check against a list or tuple of class.
isinstance([], (list, tuple))
This isinstance check meets the purpose if you are accepting only a list or tuple. But here the case is different, there is no such restriction. So, this solution is not extensible for a developer who uses your library to send something else other than a list or tuple. Here comes the importance of abstract class. Let’s understand through the below code.
Python3
import abc class MySequence(metaclass = abc.ABCMeta): pass MySequence.register( list ) MySequence.register( tuple ) a = [ 1 , 2 , 3 ] b = ( 'x' , 'y' , 'z' ) print ( 'List instance:' , isinstance (a, MySequence)) print ( 'Tuple instance:' , isinstance (b, MySequence)) print ( 'Object instance:' , isinstance ( object (), MySequence)) |
Output:
List instance: True Tuple instance: True Object instance: False
As you can see, when you do isinstance check, it returns true for both the list and tuple; for the other objects, it returns false. Let’s consider a scenario where a developer expects a class object itself. In the above case, the isinstance will return false. But it can be achieved by creating a custom class and registering it with the abstract base class.
Here ‘MySequence’ is an abstract class within the library. A developer can import it and register a custom class. Let’s have a look at the below code.
Python3
import abc class MySequence(metaclass = abc.ABCMeta): pass class CustomListLikeObjCls( object ): pass MySequence.register(CustomListLikeObjCls) print ( issubclass (CustomListLikeObjCls, MySequence)) |
Output:
True
Here, CustomListLikeObjCls instance is passed to the library by registering it with MySequence. Therefore, the instance check returns True. Apart from the above method, you can also use the register method as a decorator to register a custom class. Let’s see how to use the register method as a decorator.
Python3
import abc class MySequence(metaclass = abc.ABCMeta): pass @MySequence .register class CustomListLikeObjCls( object ): pass print ( issubclass (CustomListLikeObjCls, MySequence)) |
Output:
True
Registering a class using the above-implemented method meets the purpose. However, you have to do manual registration for every intended subclass. How about automatic subclassing based on a particular method?. An abstract class has a concept called __subclasshook__ to subclass the classes.
It is a special magic method defined by ABCMeta. The __subclasshook__ must be defined as a class method using @classmethod decorator. It takes one additional positional argument other than the class and can return either of the three values – True, False, or NotImplemented. Let’s look at the below implementation.
Python3
import abc class AbstractClass(metaclass = abc.ABCMeta): @classmethod def __subclasshook__( cls , other): print ( 'subclass hook:' , other) hookmethod = getattr (other, 'hookmethod' , None ) return callable (hookmethod) class SubClass( object ): def hookmethod( self ): pass class NormalClass( object ): hookmethod = 'hook' print ( issubclass (SubClass, AbstractClass)) print ( issubclass (NormalClass, AbstractClass)) |
Output:
subclass hook: <class '__main__.SubClass'> True subclass hook: <class '__main__.NormalClass'> False
From the above discussion, you understood how to hook subclasses automatically. Now we will look into how to avoid instantiating a subclass that doesn’t override a particular method in the superclass. This feature can be achieved using @abc.abstractmethod.
@abc.abstractmethod
@abc.abstractmethod prevents any attempt to instantiate a subclass that doesn’t override a particular method in the superclass. Let’s have a look at the below code:
Python3
import abc class AbstractClass(metaclass = abc.ABCMeta): @abc .abstractmethod def abstractName( self ): pass class InvalidSubClass(AbstractClass): pass isc = InvalidSubClass() |
Since the InvalidSubclass doesn’t override the method abstractName, the @abc.abstractmethod prevents the subclass from instantiation and throws the below error.
Traceback (most recent call last):
File “/home/553d5199a662239eae3ff58efb37b6ec.py”, line 11, in <module>
isc = InvalidSubClass()
TypeError: Can’t instantiate abstract class InvalidSubClass with abstract methods abstractName
Let’s look into another example where the subclass overrides the abstract method.
Python3
import abc class AbstractClass(metaclass = abc.ABCMeta): @abc .abstractmethod def abstractName( self ): pass class ValidSubClass(AbstractClass): def abstractName( self ): return 'Abstract 1' vc = ValidSubClass() print (vc.abstractName()) |
Output:
Abstract 1
Next, let’s see how to declare properties as an abstract class.
Abstract Base Class (abc) in Python
Have you ever thought about checking whether the objects you are using adheres to a particular specification? It is necessary to verify whether an object implements a given method or property, especially while creating a library where other developers make use of it. A developer can use hasattr or isinstance methods to check whether the input conforms to a particular identity. But sometimes it is inconvenient to use those methods to check a myriad of different properties and methods.
As a solution to this inconvenience, Python introduced a concept called abstract base class (abc). In this section, we will discuss the abstract base class and its importance.
- Abstract Base Class
- Declaring an Abstract Base Class
- Why declare an Abstract Base Class?
- Abstract Properties
- Built-In Abstract Classes