3 Alternatives for Regular Custom Classes in Python | by Yong Cui | Apr, 2022

Photo by Headway on Unsplash

Most time in any project, we’re working to define a variety of classes to model our data. Thus, building proper classes is an essential technique to make your project robust and maintainable. In general, a custom class takes the following form:

class CustomClass
# the body of the class
pass

However, there are several situations where you want to use something different. In this article, I’ll review three alternatives related to defining classes for your project.

We know that tuples are one of the basic built-in data structures in Python. When we define a tuple, we simply use parentheses to enclose its items separated by commas, as below:

student_data = ("John", "M")

As a built-in type, tuples are designed to be generic such that they can serve general purposes. However, this generality has a cost — tuples don’t know what data they’re holding. For tuples, they’re not “defined” by us and they can hold any item as they want without any restriction. By contrast, a custom class knows what data, in particular, the attributes, it holds:

Auto-completion Hints

As shown above, in the PyCharm IDE, you can see that the student‘s attributes are automatically populated for code completion because the IDE knows what data a student can have.

To provide a “normal” class’s benefits (eg, dot notations), you can actually create a data model that is based on tuples termed named tuples:

Named tuples

To create a named tuple, you import the namedtuple function from the built-in collections module. The namedtuple function can be referred to as a factory function because you use it to create a new class. Specifically, you provide the name for the class as a string and the attributes as a list. For an instance of the named tuple, it knows what data such instance holds.

Question: When do you want to use named tuples over a regular custom class?

Answer: When the data model serves as a data container, you can use named tuples, because they don’t support mutability, which means that you can’t change a named tuple instance’s attributes, an instance of a regular class. Notably, named tuples are a subclass of tuples, so they’re small in size. If you need to create many instances, named tuples save memory.

For more information about named tuples, please refer to my previous article.

Enumeration is a technique that involves creating a class that holds related members of the same concept together. For example, north, south, east, and west are the members of the direction concept. For another example, spring, summer, fall, and winter are the members of the season concept.

In the standard library of Python, the enum module provides the essential functionalities for creating an enumeration class. Let’s see some code first:

from enum import Enum

class Season(Enum):
SPRING = 1
SUMMER = 2
FALL = 3
WINTER = 4

As you can see, the Season class is a subclass of Enum, which holds the four seasons. Here, I capitalize these four members, as they represent constants. However, it’s your choice if you prefer a lowercase. Each member has two important attributes: name and valueas shown below:

>>> spring = Season.SPRING
>>> spring.name
'SPRING'
>>> spring.value
1

For the values, you can name them incremental integers. They can be useful if you want to construct an enumeration member. Suppose that you have an API and receive a response of 2, as the value for the season. You can create a member as below:

>>> fetched_season_value = 2
>>> matched_season = Season(fetched_season_value)
>>> matched_season
<Season.SUMMER: 2>

Another useful feature of the enumeration class is supporting iteration. That is, the enumeration class is iterable. For example, you can create a list of these enumeration numbers, by simply running below:

>>> list(Season)
[<Season.SPRING: 1>, <Season.SUMMER: 2>, <Season.FALL: 3>, <Season.WINTER: 4>]

You can also use list comprehension if you want to get the names of these four seasons:

>>> [x.name for x in Season]
['SPRING', 'SUMMER', 'FALL', 'WINTER']

Question: When do you want to use enumeration over a regular custom class?

Answer: When you have a group of members that fall in the same concept, you should use enumeration. Although you can create a regular class and use class attributes to hold these members, the regular class doesn’t support iteration by default. In addition, it doesn’t have native attributes, such as name and value, to manipulate these members.

For more information about enumeration, please refer to my previous article.

When I say data classes, I simply mean that we’re creating a class to hold data using the dataclass decorator. Unlike a typical decorator decorating a function, the dataclass decorator decorates a class, as shown below:

from dataclasses import dataclass

@dataclass
class Student:
name: str
gender: str

The dataclass decorator is part of the dataclasses module. We simply place this decorator above the class that we’re defining. In the body of the class, we specify the attributes for the class together with their respective types.

The decoration seems very straightforward. Let’s see what these extra functionalities are for the dataclass decorator.

>>> student = Student("John", "M")
>>> student.name
'John'
>>> student.gender
'M'

One significant thing as shown above is that we didn’t explicitly define the __init__ method, but the decorated Student class knows how to construct an instance! It’s because that the dataclass decorator uses the annotated attributes (eg, name: str & gender: str) to create the initialization __init__ method for us under the hood.

Besides the __init__ method, the dataclass decorator also implements the __repr__ method for us, which allows us to inspect the instance:

>>> repr(student)
"Student(name='John', gender='M')"

Notably, because __repr__ is the fallback if a class doesn’t implement __str__. So if you print an instance of the data class, you’ll get the same string output:

>>> print(student)
Student(name='John', gender='M')

Question: When do you want to use data class over a regular custom class?

Answer: When you want to eliminate some boilerplate for a custom class, such as implementing __init__ and __repr__. If you want more customized behavior in the __init__ method, the dataclass decorator may not be your best bet.

For more information about enumeration, please refer to my previous article.

In this article, we reviewed three alternatives that we can consider other than a regular custom class: named tuples, enumeration, and data classes. Each of them has its pros and cons, and you should choose the appropriate data model based on your needs. Don’t restrict yourself to regular custom classes, and there might be better built-in solutions for you.

Remember that don’t reinvent the wheel!

Leave a Comment