This bad boy is like a secret weapon that can make your generic classes more versatile than a Swiss Army knife on juice. But before we dive into this magical land, let’s first understand what it does and why you should care about it.
To set the stage `__class_getitem__` allows us to create customized versions of standard-library generic classes at runtime based on type hints. This means that instead of having to define a new class for every possible combination of types, we can simply pass in the desired type hint as an argument and let Python do the heavy lifting for us.
Here’s how it works: when you call `MyGenericClass[int]` or `MyGenericClass[‘str’]`, instead of creating a new instance of your generic class with those types, Python will first check if there is a special method called `__class_getitem__` defined on the class. If so, that method will be called with two arguments: the type hint and the original class object itself (i.e., `cls`).
The purpose of this method is to return an instance of your generic class that has been customized based on the given type hints. This can involve adding or removing methods, changing attribute types, or even modifying the behavior of existing methods. The possibilities are endless!
Now let’s see how we can use `__class_getitem__` to create a custom version of Python’s built-in list class that only allows us to append strings:
# Creating a custom version of Python's built-in list class that only allows appending strings
# Define a new class called StringList that inherits from the built-in list class
class StringList(list):
# Define the constructor method, which takes in any number of arguments and passes them to the parent class's constructor
def __init__(self, *args):
# Call the parent class's constructor using the super() function
super().__init__(*args)
# Define the __setitem__ method, which is used to set values at a specific index in the list
def __setitem__(self, key, value):
# Check if the value being set is not a string
if not isinstance(value, str):
# If it is not a string, raise a TypeError
raise TypeError("Only strings can be appended to this list")
# If it is a string, call the parent class's __setitem__ method to set the value at the specified index
return super().__setitem__(key, value)
# Define the append method, which is used to add a new item to the end of the list
def append(self, item: str):
# Call the __setitem__ method to set the value at the end of the list
self.__setitem__(len(self), item)
# Define the __class_getitem__ method, which is used to create a custom version of the class based on the type hint provided
def __class_getitem__(cls, key):
# Check if the key is a type hint
if isinstance(key, type):
# If it is a type hint, create a new class called CustomStringList that inherits from the built-in list class
class CustomStringList(list):
# Define the constructor method, which takes in any number of arguments and passes them to the parent class's constructor
def __init__(self, *args):
# Call the parent class's constructor using the super() function
super().__init__(*args)
# Define the append method, which is used to add a new item to the end of the list
def append(self, item: str):
# Call the __setitem__ method to set the value at the end of the list
self.__setitem__(len(self), item)
# Return the new class, with the name and module of the original class, and the type hint added to the class name
return type("CustomStringList", (cls, ), {"__module__": cls.__module__, "__qualname__": f"{cls.__qualname__}['str']"})(key())
# If the key is not a type hint, just call the regular __getitem__ method
return super().__getitem__(key)
In this example, we’ve created a custom version of Python’s built-in list class called `StringList`. This new class has an additional constraint that only allows us to append strings. We’ve also added some type hinting for the `append()` method and defined our own implementation of `__class_getitem__`.
The magic happens in the last line, where we use a metaclass trick to create a new class called “CustomStringList” that inherits from both our original list class (i.e., `cls`) and Python’s built-in type object for strings (i.e., `type(‘str’)`). This allows us to return an instance of this customized version when someone calls `MyGenericClass[‘str’]`.
And the best part? You don’t even need to be a Python ninja to do it anyone with basic knowledge of object-oriented programming should be able to follow along.