Abstract Classes#

Overview

Questions:

  • What are Abstract Classes?

  • Why should I use Abstract Classes?

Objectives:

  • Understand the concepts behind Abstract Classes.

Abstract Classes#

An abstract class is a way to define how a class will be designed while allowing some or all of its methods to remain unimplemented. An abstract class can be inherited by another abstract class, in which case it is usually extended into a larger abstract class by the child abstract class, or it can be inherited by a class, which implements at least all of the abstract methods from the abstract class. An abstract class creates a structure for code to look for without defining how the methods are implemented. Any methods implemented within an abstract class can be used by their children or overridden to achieve new behavior.

In python, we construct abstract classes using a package from the standard library. We first need to import the abstract base class and abstract method decorator.

from abc import ABC, abstractmethod

ABC is the abstract base class. Inheriting from the abstract base class enforces that any child classes of the abstract class must have some implementation of any abstract methods.

class AbstractClass(ABC):

Here we define the name of our abstract class. Note ABC in the class definition to denote the inheritance. Then we create a definition for each method we would like any of the abstract classes children to implement and any methods we would like to pass on.

    @abstractmethod
    def first_method(self):
        pass

    @abstractmethod
    def second_method(self):
        pass
        
    def third_method(self):
        print('This method is implemented for use by any child classes.')

Since we have used the @abstractmethod decorator for some of these methods, any child class must implement a method with the same name as the abstract class.

If we try and create an instance of this object, we will notice a problem.

abstract_class = AbstractClass()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods first_method, second_method

Since there are unimplemented methods, we canot create the instance of the object.

A child class that inherits our abstract class will need to override first_method and second_method if it is not itself an abstract class.

class ChildClass(AbstractClass):
    def first_method(self):
        print('Implementing the first method inherited from my parent.')
    
    def second_method(self):
        print('Implementing the second method inherited from my parent.')

Note that we did not implement the third_method in the child class. Since third_method is not an abstract method, we can utilize the implementation provided by our parent.

Lets try and create and instance of the child class.

child_class = ChildClass()

We should see a blank output as the object has been created correctly. We can now use all three methods on our child object.

child_class.first_method()
child_class.second_method()
child_class.third_method()
Implementing the first method inherited from my parent.
Implementing the second method inherited from my parent.
This method is implemented for use by any child classes.

If we wish to override the method provided by the abstract class, we can simply re-write the method.

class ChildClass(AbstractClass):
    def first_method(self):
        print('Implementing the first method inherited from my parent.')
    
    def second_method(self):
        print('Implementing the second method inherited from my parent.')
    
    def third_method(self):
        print('Overriding the third method provided by my parent.')

Re-instantiating the object and running the three methods, we will see the overridden output.

child_class = ChildClass()

child_class.first_method()
child_class.second_method()
child_class.third_method()
Implementing the first method inherited from my parent.
Implementing the second method inherited from my parent.
Overriding the third method provided by my parent.

Molecule Example#

Given that we have already created a Molecule class, let us assume that we wanted it to inherit from a base class. Consider the following base class as an example:

class MolBase(ABC):
    @abstractmethod
    def compute_center_of_mass():
        pass

MolBase inherits from the abstract base class and has a single abstract method. Trying to instantiate an object of type MolBase, we get an error, as MolBase has an abstract method.

mol1 = MolBase()
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_1250/3850661068.py in <module>
----> 1 mol1 = MolBase()

TypeError: Can't instantiate abstract class MolBase with abstract methods compute_center_of_mass

If we change the Molecule class to inherit from MolBase and try and create a molecule we will get a similar error:

class Molecule(MolBase):
    def __init__(self, name, charge, symbols, coordinates):
        self.name = name
        self.charge = charge
        self.symbols = symbols
        self.coordinates = coordinates

    @property
    def symbols(self):
        return self._symbols
        
    @symbols.setter
    def symbols(self, symbols):
        self._symbols = symbols
        self._update_num_atoms()

    def _update_num_atoms(self):
        self.num_atoms = len(self.symbols)

    def __str__(self):
        return f'name: {self.name}\ncharge: {self.charge}\nsymbols: {self.symbols}\ncoordinates: {self.coordinates}\nnumber of atoms: {self.num_atoms}'

mol1 = Molecule(name='water molecule', charge=0.0, symbols=["O", "H", "H"], coordinates=[[0,0,0],[0,1,0],[0,0,1]])
print(mol1)
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_1250/1044270064.py in <module>
----> 1 mol1 = Molecule(name='water molecule', charge=0.0, symbols=["O", "H", "H"], coordinates=[[0,0,0],[0,1,0],[0,0,1]])
      2 print(mol1)

TypeError: Can't instantiate abstract class Molecule with abstract methods compute_center_of_mass

To implement the center of mass for Molecule, we will also need to pass in a set of masses for the atoms, which we will do in the form of a list.

The full class with masses and an implemented compute_center_of_mass() method:

class Molecule(MolBase):
    def __init__(self, name, charge, symbols, coordinates, masses):
        self.name = name
        self.charge = charge
        self.symbols = symbols
        self.coordinates = coordinates
        self.masses = masses

    @property
    def symbols(self):
        return self._symbols
        
    @symbols.setter
    def symbols(self, symbols):
        self._symbols = symbols
        self._update_num_atoms()

    def _update_num_atoms(self):
        self.num_atoms = len(self.symbols)

    def compute_center_of_mass(self):
        com_x = 0
        com_y = 0
        com_z = 0
        total_mass = 0
        for mass, coordinate in zip(self.masses, self.coordinates):
            total_mass += mass
            com_x += mass*coordinate[0]
            com_y += mass*coordinate[1]
            com_z += mass*coordinate[2]
        return [com_x/total_mass, com_y/total_mass, com_z/total_mass]

    def __str__(self):
        return f'name: {self.name}\ncharge: {self.charge}\nsymbols: {self.symbols}\ncoordinates: {self.coordinates}\nnumber of atoms: {self.num_atoms}'

We can create an instance of Molecule and try and run the method.

mol1 = Molecule(name='water molecule', charge=0.0, symbols=["O", "H", "H"], coordinates=[[0,0,0],[0,1,0],[0,0,1]], masses=[15.999, 1.00784, 1.00784])
print(mol1)
print(mol1.compute_center_of_mass())
name: water molecule
charge: 0.0
symbols: ['O', 'H', 'H']
coordinates: [[0, 0, 0], [0, 1, 0], [0, 0, 1]]
number of atoms: 3
[0.0, 0.05594548446045114, 0.05594548446045114]

Having implemented the abstract method, our class is instantiable. Note however, that python does not concern itself with the specifics of the implementation. Python’s only concern is that a non-abstract method exists with the same name. The input parameters and implementation will not be checked.

Key Points

  • Abstract Classes

  • Abstract Methods

  • Overwritting Abstract Methods