Python Abstract Class

Create a Python Abstract Class: A Step-By-Step Guide

Knowing how to create an abstract class in Python is a must-know for Python developers. In this guide you will learn how to define and use abstract classes.

But first of all, what is an abstract class?

An Abstract class is a template that enforces a common interface and forces classes that inherit from it to implement a set of methods and properties. The Python abc module provides the functionalities to define and use abstract classes.

Abstract classes give us a standard way of developing our code even if you have multiple developers working on a project.

Let’s find out how to use them in your programs!

A Simple Example of Abstract Class in Python

Abstract Classes come from PEP 3119. PEP stands for Python Enhancement Proposal and it’s a type of design document used to explains new features to the Python community.

Before starting, I want to mention that in this tutorial I will use the following version of Python 3:

$ python --version
Python 3.7.4

The reason why I’m highlighting this is that the way abstract classes are defined can change depending on the version of Python you use. This applies, for example, to Python 2 but also to earlier versions of Python 3 (lower than 3.4).

Firstly, let’s start by defining a simple class called Aircraft:

class Aircraft:
  
    def fly(self):
        pass

I can create an instance of this class and call the fly method on it without any errors:

aircraft1 = Aircraft()
aircraft1.fly()

Now, let’s say I want to convert this class into an abstract class because I want to use it as common interface for classes that represent different types of aircrafts.

Here is how we can do it…

  • Import ABC and abstractmethod from the Python abc module.
  • Derive our Aircraft class from ABC.
  • Add the @abstractmethod decorator to the fly method.
from abc import ABC, abstractmethod

class Aircraft(ABC):
  
    @abstractmethod
    def fly(self):
        pass

You can make a method abstract in Python by adding the @abstractmethod decorator to it.

Now, when we create an instance of Aircraft we see the following error:

aircraft1 = Aircraft()

Traceback (most recent call last):
  File "aircraft.py", line 10, in <module>
    aircraft1 = Aircraft()
TypeError: Can't instantiate abstract class Aircraft with abstract methods fly

As you can see I’m not able to create an instance of our abstract class.

An Abstract class is a class that contains one or more abstract methods. Abstract classes cannot be instantiated.

This means that we cannot create an object from an abstract class…

So, how can we use them?

Abstract classes can only be used via inheritance and their concrete child classes have to provide an implementation for all the abstract methods.

In the next section you will see what I mean with concrete.

Python Abstract Class Inheritance

Let’s see what happens if instead of instantiating our abstract class we create a child class that derives from it.

from abc import ABC, abstractmethod

class Aircraft(ABC):
  
    @abstractmethod
    def fly(self):
        pass

class Jet(Aircraft):
    pass

Let’s try to create an instance of Jet:

jet1 = Jet()

Traceback (most recent call last):
  File "aircraft.py", line 13, in <module>
    jet1 = Jet()
TypeError: Can't instantiate abstract class Jet with abstract methods fly

Hmmm…a similar error to the one we have seen before with the difference that now it refers to the Jet class.

Why?

That’s because to be able to create an object of type Jet we have to provide an implementation for the abstract method fly() in this class.

Let’s give it a try:

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

This time I can create an instance of type Jet and execute the fly method:

jet1 = Jet()
jet1.fly()

$ python aircraft.py 
My jet is flying

A class that inherits an abstract class and implements all its abstract methods is called concrete class. In a concrete class all the methods have an implementation while in an abstract class some or all the methods are abstract.

Now I will add another abstract method called land() to our Aircraft abstract class and then try to create an instance of Jet again:

class Aircraft(ABC):

    @abstractmethod
    def fly(self):
        pass

    @abstractmethod
    def land(self):
        pass

Here’s what happens when I create an instance of the class Jet whose implementation hasn’t changed:

jet1 = Jet()

Traceback (most recent call last):
  File "aircraft.py", line 18, in <module>
    jet1 = Jet()
TypeError: Can't instantiate abstract class Jet with abstract methods land

The error is caused by the fact that we haven’t provided a concrete implementation for the land method in the Jet subclass. This demonstrates that to instantiate a class that derives from an abstract class we have to provide an implementation for all the abstract methods inherited from the parent abstract class.

A child class of an abstract class can be instantiated only if it overrides all the abstract methods in the parent class.

The term override in Python inheritance indicates that a child class implements a method with the same name as a method implemented in its parent class. This is a basic concept in object oriented programming.

Makes sense?

So, let’s implement the land() method in the Jet class:

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

    def land(self):
        print("My jet has landed")

I will run both methods in the Jet class to make sure everything works as expected:

jet1 = Jet()
jet1.fly()
jet1.land()

$ python aircraft.py 
My jet is flying
My jet has landed

All good!

Using Super to Call a Method From an Abstract Class

An abstract method in Python doesn’t necessarily have to be completely empty.

It can contain some implementation that can be reused by child classes by calling the abstract method with super(). This doesn’t exclude the fact that child classes still have to implement the abstract method.

Here is an example…

We will make the following changes:

  • Add a print statement to the land method of the Aircraft abstract class.
  • Call the land method of the abstract class from the Jet child class before printing the message “My jet has landed”.
from abc import ABC, abstractmethod

class Aircraft(ABC):

    @abstractmethod
    def fly(self):
        pass

    @abstractmethod
    def land(self):
        print("All checks completed")

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

    def land(self):
        super().land()
        print("My jet has landed")

Here is the output:

jet1 = Jet()
jet1.land()

$ python aircraft.py 
All checks completed
My jet has landed

From the output you can see that the jet1 instance of the concrete class Jet calls the land method of the abstract class first using super() and then prints its own message.

This can be handy to avoid repeating the same code in all the child classes of our abstract class.

How to Implement an Abstract Property in Python

In the same way we have defined abstract methods we can also define abstract properties in our abstract class.

Let’s add an attribute called speed to our abstract base class Aircraft and also property methods to read and modify its value.

A way in Python to define property methods to read and modify the value of speed would be the following:

class MyClass:

...
...
    @property
    def speed(self):
        return self.__speed

    @speed.setter
    def speed(self, value):
        self.__speed = value

The method with the @property decorator is used to get the value of speed (getter). The method with the @speed.setter decorator allows to update the value of speed (setter).

In this case we want these two methods to be abstract in order to enforce their implementation in every subclass of Aircraft. Also, considering that they are abstract we don’t want to have any implementation for them…

…we will use the pass statement.

We are also adding an abstract constructor that can be called by its subclasses.

To be fair I’m tempted to remove this constructor considering that an abstract class is not supposed to be instantiated.

At the same time the constructor can be used as a guidance for the subclasses that will have to implement it. I will keep it for now.

So, the Aircraft class looks like this:

class Aircraft(ABC):

    @abstractmethod
    def __init__(self, speed):
        self.__speed = speed

    @property
    @abstractmethod
    def speed(self):
        pass

    @speed.setter
    @abstractmethod
    def speed(self, value):
        pass

    @abstractmethod
    def fly(self):
        pass

    @abstractmethod
    def land(self):
        print("All checks completed")

As you can see, I have added the @abstractmethod decorator to the property methods.

Note: it’s important to specify the @abstractmethod decorator after the @property and @speed.setter decorators. If I don’t do that I get the following error:

$ python aircraft.py
Traceback (most recent call last):
  File "aircraft.py", line 3, in <module>
    class Aircraft(ABC):
  File "aircraft.py", line 10, in Aircraft
    @property
  File "/Users/codefather/opt/anaconda3/lib/python3.7/abc.py", line 23, in abstractmethod
    funcobj.__isabstractmethod__ = True
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable

Let’s also override the constructor in the Jet class:

class Jet(Aircraft):

    def __init__(self, speed):
        self.__speed = speed

    def fly(self):
        print("My jet is flying")

I’m curious to see what happens if we try to create an instance of the Jet class after adding the abstract property methods to its parent abstract class.

Notice that I’m passing the speed when I create an instance of Jet considering that we have just added a constructor to it that takes the speed as argument:

jet1 = Jet(900)

$ python aircraft.py 
Traceback (most recent call last):
  File "aircraft.py", line 45, in <module>
    jet1 = Jet(900)
TypeError: Can't instantiate abstract class Jet with abstract methods speed

The error message is telling us that we need to implement the property methods in the concrete class Jet if we want to instantiate it.

Let’s do it!

class Jet(Aircraft):

    def __init__(self, speed):
        self.__speed = speed

    @property
    def speed(self):
        return self.__speed

    @speed.setter
    def speed(self, value):
        self.__speed = value

    def fly(self):
        print("My jet is flying")

    def land(self):
        super().land()
        print("My jet has landed")

And here is the output when we use the concrete property methods in the Jet class to read and update the value of the speed:

jet1 = Jet(900)
print(jet1.speed)
jet1.speed = 950
print(jet1.speed)

$ python aircraft.py 
900
950

The code works fine!

Python also provides a decorator called @abstractproperty. Do we need it?

According to the official Python documentation this decorator is deprecated since version 3.3 and you can stick to what we have seen so far.

How to Define Abstract Classes in Earlier Versions of Python

In this tutorial we have focused on Python 3.4 or above when it comes to defining abstract classes. At the same time I want to show you how you can define an abstract class with Python 3.0+ and with Python 2.

I will also include Python 3.4+ in the examples so you can quickly compare it with the other two ways of defining an abstract class.

Firstly, let’s recap the implementation we have used in this tutorial using Python 3.4+.

To make things simple I will show the:

  • Imports from the Python abc module.
  • Abstract Class definition.
  • Definition of the abstract method fly().
from abc import ABC, abstractmethod

class Aircraft(ABC):

    @abstractmethod
    def fly(self):
        pass

Here is the same code for Python 3.0+:

from abc import ABCMeta, abstractmethod

class Aircraft(metaclass=ABCMeta):

    @abstractmethod
    def fly(self):
        pass

ABCMeta is a metaclass that allows to define abstract base classes. A metaclass is a class used to create other classes.

I’m curious to know more about this metaclass. Let’s look at its MRO (Method Resolution Order):

from abc import ABCMeta

print(ABCMeta.__mro__)
(<class 'abc.ABCMeta'>, <class 'type'>, <class 'object'>)

As you can see it derives from “type”.

And now let’s move to Python 2. I’m using version 2.7.14:

from abc import ABCMeta, abstractmethod

class Aircraft:
    __metaclass__ = ABCMeta

    @abstractmethod
    def fly(self):
        pass

This should help you define your abstract classes with different versions of Python!

Raising a NotImplementedError Instead of Using the abstractmethod Decorator

Before completing this tutorial I want to show you an approach that you can use to obtain a similar behaviour in an abstract class that the abstractmethod decorator provides.

Let’s take a subset of our Aircraft abstract class and remove the @abstractmethod decorator from the fly() method:

from abc import ABC, abstractmethod

class Aircraft(ABC):

    def fly(self):
        pass

In the way our code looks like right now we can create an instance of our abstract class. When I execute the following I don’t see any errors:

aircraft1 = Aircraft()

Obviously this is not what we want considering that an abstract class is not done to be instantiated.

What could we do to prevent objects of type Aircraft from being created?

I could implement a constructor that raises an exception when it’s invoked…

from abc import ABC, abstractmethod

class Aircraft(ABC):

    def __init__(self):
        raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")

    def fly(self):
        pass

Let’s try to create an instance now:

aircraft1 = Aircraft()

Traceback (most recent call last):
  File "abstract_test.py", line 12, in <module>
    aircraft1 = Aircraft()
  File "abstract_test.py", line 6, in __init__
    raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")
TypeError: TypeError: This is an Abstract Class and it cannot be instantiated

It didn’t allow me to create an instance. That’s good!

Obviously the most correct option is to use the @abstractmethod decorator. With this example we just want to get used to come up with different approaches in our programs.

Let’s see what happens if our Jet subclass inherits this version of the Aircraft class. At the moment I don’t have a constructor in the Jet class.

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

When I create an instance of Jet, here is what happens:

jet1 = Jet()

Traceback (most recent call last):
  File "abstract_test.py", line 20, in <module>
    jet1 = Jet() 
  File "abstract_test.py", line 6, in __init__
    raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")
TypeError: TypeError: This is an Abstract Class and it cannot be instantiated

Same error as before, but this time it doesn’t make a lot of sense. The error message says that this is an abstract class and I can’t instantiate it.

Let’s see if we can improve this exception and error message…

We could raise a NotImplementedError instead to clearly show that methods in the abstract class need to be implemented in its subclasses.

from abc import ABC, abstractmethod

class Aircraft(ABC):

    def __init__(self):
        raise NotImplementedError("NotImplementedError: Implementation required for __init__() method")

    def fly(self):
        raise NotImplementedError("NotImplementedError: Implementation required for fly() method")

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

Now, let’s create an instance of Jet:

jet1 = Jet()

Traceback (most recent call last):
  File "new_abstract.py", line 17, in <module>
    jet1 = Jet()
  File "new_abstract.py", line 6, in __init__
    raise NotImplementedError("NotImplementedError: Implementation required for __init__() method")
NotImplementedError: NotImplementedError: Implementation required for __init__() method

The message is clearer now…

…let’s define a constructor in the subclass Jet:

class Jet(Aircraft):

    def __init__(self, speed):
        self.__speed = speed

    def fly(self):
        print("My jet is flying at a speed of {} km/h".format(self.__speed))

Now we can create an instance of Jet and execute the fly method:

jet1 = Jet(900)
jet1.fly()

My jet is flying at a speed of 900 km/h

All good this time!

Conclusion

Have you enjoyed this tutorial? We went through quite a lot!

We have started by explaining the concept of abstract classes that represent a common interface to create classes that follow well defined criteria. The Python module that we have used in all the examples is called ABC (Abstract Base Classes).

Abstract classes cannot be instantiated and they are designed to be extended by concrete classes that have to provide an implementation for all the abstract methods in their parent class.

You have also learned that the @abstractmethod decorator allows to define which methods are abstract. It also applies to property methods to enforce the implementation of getters and setters.

Finally, we have seen how to define abstract classes with different versions of Python and how we could “simulate” the behaviour of the @abstractmethod decorator by raising exceptions in the methods of our abstract class.

Are you completely new to abstract classes or have you used them before?

Let me know in the comments below!

Another important concept of object oriented programming in Python is inheritance (we have covered some concepts about inheritance in this tutorial too).

Have a look at this Codefather article about Python class inheritance.

Share knowledge with your friends!

Leave a Reply

Your email address will not be published. Required fields are marked *