Today were going to talk about something that might seem a little boring at first glance abstract base classes (ABCs) in the collections module of Python. But don’t let their name fool you, these guys are actually pretty cool and can save us some serious headaches when working with data structures.
So what exactly is an ABC? Well, its not a fancy dance move or a new type of beer (although that would be awesome). In programming terms, an ABC is a class that defines a set of methods that must be implemented by its subclasses in order to be considered valid instances of the base class.
In other words, if you want your custom data structure to behave like a list or a dictionary, you can create an ABC that inherits from one of these built-in classes and implements all their required methods. This way, you don’t have to worry about implementing every single method yourself the base class takes care of most of it for you!
But why would we want to do this? Well, lets say you need a custom data structure that behaves like a list but has some additional functionality. Instead of writing all those methods from scratch (which can be time-consuming and error-prone), you can simply inherit from the built-in `list` class and implement only the methods that are specific to your new data structure.
Here’s an example:
# Importing the abc module from collections library
from collections import abc
# Creating a new class called MyList that inherits from the ABC class and the built-in list class
class MyList(abc.ABC, list):
# Defining the constructor method with *args as a parameter
def __init__(self, *args):
# Calling the constructor method of the list class and passing in the arguments
super().__init__(*args)
# Defining a property method called is_sorted
@property
def is_sorted(self):
# Using the all() function to check if all elements in the list are in sorted order
# The zip() function is used to iterate through two lists simultaneously
# The comparison operator <= is used to check if the elements are in ascending order
return all(a <= b for a, b in zip(self, self[1:]))
# Defining a method called sort_by with a default key function
def sort_by(self, key=lambda x: x):
# Using the sort() method of the list class to sort the list using the given key function
self.sort(key=key)
# Defining the __contains__ method to check if an item is present in the list
def __contains__(self, item):
# Using the any() function to check if any element in the list is equal to the given item
return any(x == item for x in self)
# Defining an abstract method called my_custom_functionality
@abc.abstractmethod
def my_custom_functionality(self):
# This method will be implemented in a subclass and will provide custom functionality
pass
In this example, we’re creating a new class called `MyList` that inherits from both the built-in `list` and the `ABC` class (which is part of Pythons collections module). This allows us to use all the methods defined in `list`, as well as any additional functionality we need.
We’re also implementing a few new methods, such as `is_sorted` and `sort_by`. These are just examples you can add whatever functionality your custom data structure needs!
But what about that `@abc.abstractmethod` decorator? This tells Python that the method defined in this class must be implemented by any subclasses of `MyList`, otherwise a `TypeError` will be raised at runtime. In other words, if you create a new class that inherits from `MyList` but doesn’t implement `my_custom_functionality`, you won’t be able to instantiate it!
This is where the magic happens by defining an ABC for our custom data structure, we can ensure that any subclasses of this base class will have all the required functionality. This makes our code more consistent and easier to maintain over time.
They might not be as exciting as a fancy dance move or a new type of beer, but they can definitely save us some headaches when working with data structures. Give them a try next time you need to create a custom class that behaves like one of the built-in ones I promise it’s worth your while!
But wait, theres more! Did you know that generic versions of abstract collections like Mapping or Sequence and generic versions of built-in classes List, Dict, Set, and FrozenSet cannot be instantiated? However, concrete user-defined subclasses thereof and generic versions of concrete collections can be instantiated:
# Import the abstract base class Mapping from the collections.abc module
from collections.abc import Mapping
# Create a class called MyDict that inherits from the Mapping abstract base class
class MyDict(Mapping):
# Define the constructor method, which takes in any number of arguments and assigns them to the __dict__ attribute as a dictionary
def __init__(self, *args):
self.__dict__ = dict(*args)
# Define the __getitem__ method, which takes in a key and returns the corresponding value from the __dict__ attribute
def __getitem__(self, key):
return self.__dict__[key]
# Define the __iter__ method, which returns an iterator over the keys in the __dict__ attribute
def __iter__(self):
return iter(self.__dict__)
# Define the __len__ method, which returns the length of the __dict__ attribute
def __len__(self):
return len(self.__dict__)
In this example, we’re creating a new class called `MyDict` that inherits from the abstract collection `Mapping`. This allows us to use all the methods defined in `Mapping`, as well as any additional functionality we need. Were also implementing our own dictionary-like behavior using a private attribute `__dict__`.
By defining an ABC for our custom data structure, we can ensure that any subclasses of this base class will have all the required functionality to behave like a dictionary. This makes our code more consistent and easier to maintain over time.