Abstract Base Classes in Python, Redefine Future of Banking

Category Data Engineering

Python is a dynamic and interpreted language. The result is that we do not know the errors in the code unless we run them. This is not the case with languages which are more static and compiled.

Say, you are writing a new banking API. The business case requirement is quite mission critical and so, the library needs to be future-proof. You want the future developers, who are going to use the API, to be implementing the functionality in a specific way.

Let’s say, you have your Banking class which will implement some methods. While creating a class, you are kind of making a promise that this class will implement some methods. To keep things simple, we will only take the case of one method execute_payment.

class Banking(object):
""" Payment processor that does nothing, just logs """
def __init__(self):
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logging.INFO)

def process_payment(self, destination_address, amount):
""" Execute a payment to one receiving single address

return the transaction id or None """
print("process payment for bank.")

Similarly, you have started writing implementations for various banks.

class BankingBank1(object): ...

class BankingBank2(object): ...

Now, you see a pattern in them. Being a firm believer in the DRY (Don’t Repeat Yourself) pattern, you will probably move the similar methods to a base class and the child class will implement specific functionality.

But, the subclasses may not be implementing some necessary functionality. You put that on the TODO list and go on a vacation for some days. Once back, you see code like this.

class Banking(object):
""" Payment processor that does nothing, just logs """
def __init__(self):
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logging.INFO)

def process_payment(self, destination_address, amount):
""" Execute a payment to one receiving single address

return the transaction id or None """
raise NotImplementedError("Not implemented yet")


class BankingBank1(Banking):
"""docstring for BankingBank1"""
def __init__(self, arg):
super(BankingBank1, self).__init__()
self.arg = arg


def process_payment(self, destination_address, amount):
""" Execute a payment to one receiving single address

return the transaction id or None """
print('process payment for bank 1')


class BankingBank2(Banking):
"""docstring for BankingBank1"""
def __init__(self, arg):
super(BankingBank2, self).__init__()
self.arg = arg


def user_action(bb_obj):
if isinstance(bb_obj, BankingBank1):
bb_obj.process_payment('destination_address', 100)
elif isinstance(bb_obj, BankingBank2):
print('Cannot process the payment.')
else:
print('implement this.')


bb2 = BankingBank2(__name__)
user_action(bb2)

You are appalled. There are multiple if statements checking the type of the object and then deciding the course of action based on that. Classic LBYL. You make a mental note about educating everyone in the team why LBYL is bad. A champ of the EAFP principle, you refactor the code.

class Banking(object): ...

class BankingBank1(Banking): ...

class BankingBank2(Banking): ...

def user_action(bb_obj):
try:
bb_obj.process_payment('destination_address', 100)
except NotImplementedError as e:
print('Cannot process the payment.')


bb2 = BankingBank2(__name__)
user_action(bb2)

But you are not happy. The issue with this kind of implementation is that the object gets created and error is not raised till the method is called. This might be a problem if some other methods are getting executed before this method is called. This is a problem in interpreted languages like Python. In compiled languages, this kind of error would get raised at the compilation phase itself.

Things should fail fast and if possible at the instantiation level itself, isn’t it? There goes your mentor, the programming friend who always helps out and lo, he tells you that it can be achieved using the abstract base class and the abstract method decorator. He sketches out the rough basics and you start writing the code.

""" Contains payment processors for executing payments """

from abc import ABCMeta, abstractmethod

class Banking(metaclass=ABCMeta):
""" Payment processor that does nothing, just logs """
def __init__(self):
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logging.INFO)

@abstractmethod
def process_payment(self, destination_address, amount):
""" Execute a payment to one receiving single address

return the transaction id or None """
pass


class BankingBank1(Banking):
"""docstring for BankingBank1"""
def __init__(self, arg):
super(BankingBank1, self).__init__()
self.arg = arg


def process_payment(self, destination_address, amount):
""" Execute a payment to one receiving single address

return the transaction id or None """
print('process payment for bank 1')


class BankingBank2(Banking):
"""docstring for BankingBank1"""
def __init__(self, arg):
super(BankingBank2, self).__init__()
self.arg = arg

try:
bb2 = BankingBank2(__name__)
bb2.process_payment('destination_address', 100)
except TypeError as e:
print('Cannot process the payment.')

You test if it works or not. Sure enough, TypeError gets raised while trying to create the object itself.

You can now rest assure that no functionality will get implemented and the object will fail in the instantiation phase only.

You know a new pattern now and want to know some more before calling it a day. You go through various resources:

  1. Collections.abc has some common patterns that can be implemented. You can create custom types using these patterns.
  2. Some good design patterns are implemented using abstract method.

References:

Leonardo Giordani. 2016. “Abstract Base Classes in Python”.

Stackoverflow. “Why use abstract base classes in Python

Guido van Rossum. 2007. PEP 3119.

Dan Bader. Abstract Base Classes in Python

Hope you liked this!
To get regular updates on more content, learning material and general rants you can follow me on Twitter.

Ready to embark on a transformative journey? Connect with our experts and fuel your growth today!