Title: Make abstract classes in Python
An abstract class is one that is not intended to ever be instantiated. In this post, I'll described one way you can make abstract classes so you cannot instantiate them accidentally.
Abstract and Concrete
As I said, an abstract class is one that you don't intend to instantiate. A class that you do intend to instantiate is called a concrete class.
Some languages like Java, C++, and C# let you declare a class as abstract and they prevent you from instantiating those classes. Python doesn't have a notion of abstract class so you can't do that.
In Python you could make the "abstract" class's constructor raise an exception so you can't instantiate it. Unfortunately, it's a best practice to have subclasses invoke the parent class constructor to initialize shared properties and, if the parent class raises an exception, that prevents you from instantiating the subclass.
The solution is to make the parent class's constructor check the type of the new object and only raise the exception if it is the parent class.
However, you can do something almost as good. You can make the "abstract" class's constructor check the new object's type and raise an exception if it is of the abstract class.
Example
For example, suppose you have three classes:
- Person holds a person's name
- Employee is derived from Person and also holds the employee's annual salary
- Customer is also derived from Person and holds the customer's ID
Person should be abstract while Employee and Customer should be concrete.
Here's the Person class.
class Person:
'''Abstract Person class.'''
def __init__(self, first_name, last_name):
if type(self) is __class__:
raise NotImplementedError(
f'Error: {__class__.__name__} is an abstract class')
self.first_name = first_name
self.last_name = last_name
def __str__(self):
return f'{self.first_name} {self.last_name}'
The special variable __class__ provides information about the class that owns the code. The constructor statement if type(self) is __class__ compares the new object's actual class (which could be Person, Employee, or Customer) to the code's class Person. If the object's class matches the code's class, the constructor raises a NotImplementedError. The raise command uses __class__.__name__ to get the class's name instead of hard-coding "Person" into the message so you can copy and paste the same code into any abstract class.
If the new object is not Person, the constructor saves the person's first and last names. The __str__ dunder method returns the person's first and last names in a string.
Here are the Employee and Customer classes.
class Employee(Person):
'''Concrete Employee class derived form Person.'''
def __init__(self, first_name, last_name, salary):
super().__init__(first_name, last_name)
self.salary = salary
def __str__(self):
return f'{super().__str__()} ${self.salary:,.2f}'
class Customer(Person):
'''Concrete Employee class derived form Person.'''
def __init__(self, first_name, last_name, customer_id):
super().__init__(first_name, last_name)
self.customer_id = customer_id
def __str__(self):
return f'{super().__str__()} ${self.customer_id}'
These classes' constructors invoke the Person parent class's constructor and then save their additional information. Their __str__ dunder methods call the parent class's __str__ method and then add on their specific information.
Here's the example's main program, which demonstrates the three classes.
print()
try:
person = Person('Nobody', 'No one')
print(person)
except Exception as e:
print(e)
eva = Employee('Eva', 'Employee', 1000000.00)
print(eva)
carl = Employee('Carl', 'Customer', 1337)
print(carl)
First, the program enters a try except block where it tries to instantiate a Person object. The Person class's constructor realizes that this is a Person object and raises an error.
Next, the code creates an Employee object and a Customer object and prints them. This time the new object is not a Person so the Person constructor doesn't complain.
Here's the program's output.
Error: Person is an abstract class
Eva Employee $1,000,000.00
Carl Customer $1,337.00
Conclusion
Python itself doesn't have abstract classes, but it only takes two statements to effectively prevent you from instantiating a parent class while allowing derived classes to invoke the parent's constructor. Download the example to experiment with it.
|