9 Reasons Why You Should Start Using Python Dataclasses | by Ahmed Besbes | Towards Data Science

Starting from version 3.7, Python has introduced dataclasses (see PEP 557), a new feature that defines classes that contain and encapsulate data.
I recently started using this module in a couple of data science projects and I’m really enjoying it. Off the top of my head, I can think of two reasons:
Less boilerplate codeMore readability and better code maintainability
Less boilerplate code
More readability and better code maintainability
This post is a wrap-up of my first impressions: I’ll use it to introduce dataclasses and what problems they’re designed to solve and go through 9 nice features they provide. I’ll sometimes compare classes written with dataclasses to native Python implementations and spot the differences.
Less talk, more code. Let’s have a look 🔍
PS: I won’t cover everything about dataclasses, but the features we’ll go through should get you up to speed. Nevertheless, if you want an in-depth overview, have a look at the links in the resources section at the end.
Dataclasses, as the name clearly suggests, are classes that are meant to hold data. The motivation behind this module is that we sometimes define classes that only act as data containers and when we do that, we spend a consequent amount of time writing boilerplate code with tons of arguments, an ugly __init__ method and many overridden functions.
Dataclasses alleviates this problem while providing additional useful methods under the hood. Moreover, since dataclasses is relatively new in the Python ecosystem, it enforces modern practices such as type annotations.
👉 dataclasses remain classes. Therefore, you can implement any custom methods in them just like you’d do in a normal class.
Ok, let’s see them in action now.
When we define a class to store some attributes, it usually goes something like this.
This is the standard Python syntax.
When you use dataclasses, you first have to import dataclass and then use it as a decorator before the class you define.
And here’s what the previous code looks like using dataclasses.
A few things to notice about this syntax:
There is less boilerplate code: we define each attribute once and we don’t repeat ourselves
We use type annotation for each attribute. Although this doesn’t enforce type validation, it helps your text editor provide better linting if you use a type checker like mypy.Your code would still work if you don’t respect the types but your code editor will signal the inconsistencies.
dataclasses doesn’t just allow you to write more compact code. The dataclass decorator is actually a code generator that automatically adds other methods under the hood. If we use the inspectmodule to check what methods have been added to the Person class, we can see the __init__ , __eq__ and __repr__ methods: these methods are responsible for setting the attribute values, testing for equality and representing objects in a nice string format.
If we allowed the Personclass to support order (see Tip number 9 about comparison), we’d have these methods as well.
__ge__ : greater or equal
__gt__ : greater than
__le__ : lower or equal
__lt__ : lower than
You can add default values to each attribute while preserving the annotation.
👉 Keep in mind that fields without default values cannot appear after fields with default values. For example, the following code won’t work:
Thanks to the __repr__ method already added by dataclasses, instances have a nice, human-readable representation when they are printed to the screen.
This makes it easier for debugging.
This representation can be overridden to implement any custom message you want.
Instances can easily be serialized into dicts or tuples. This is very useful when your code interacts with other programs that expect these formats.
Using dataclasses, you can create objects that are read-only. All you have to do is set the frozen argument to True inside the @dataclass decorator.
When you do this, you prevent anyone from modifying the values of the attributes once the object is instantiated.
If you try to set a frozen object’s attribute to a new value, a FrozenInstanceError error will be raised.
When you define a class using the standard Python syntax and test for the equality between two instances that have the same attribute values, here’s what you’d get:
These two objects are not equal, which is normal because the Personclass doesn’t actually implement a method for testing equality. To add equality, you’d have to implement the __eq__ method yourself. And this may look like this:
This method first checks that the two objects are instances of the same class and then tests the equality between tuples of attributes.
Now if you decide to add new attributes to your class, you’d have to update the __eq__ method again. The same goes for __ge__ , __gt__ ,__le__ and __lt__ if they’re used.
This seems like unnecessary code typing, right? Fortunately, dataclasses removes this struggle.
In some situations, you may need to create an attribute that is only defined internally, not when the class is instantiated. This may be the case when the attribute has a value that depends on previously-set attributes.
Here’s where you’d use the field function from dataclasses.
By using this function and setting itsinit and repr arguments to False to create a new field called full_name, we can still instantiate the Person class without setting the full_name attribute.
This attribute doesn’t exist yet in the instance. If we try to access it, an AttributeError is thrown.
How can we set the value of full_name and still keep it out of the constructor of the class? To do this, we’ll have to use the __post_init__method.
dataclasses has a special method called __post_init__ .
As the name clearly suggests, this method is called right after the __init__ method is called.
Going back to the previous example, we can see how this method can be called to initialize an internal attribute that depends on previously set attributes.
Note that the repr argument inside the field function has been set to True to make it visible when the object is printed. We couldn’t set this argument to True in the previous example because the attribute full_name has not been created yet.
One useful feature to have when you deal with objects that contain data is the ability to compare them and sort them in any order you intend.
By default, dataclasses implements __eq__ . To allow the other types of comparison (__lt__ (less than), __le__ (less or equal), __gt__ (greater than) and __ge__ (greater or equal)), we have to set the order argument to True in the@dataclass decorator.
@dataclasses(order=True)
The way these comparison methods are implemented take every defined field and compare them in the order they are defined until there’s a value that’s not equal.
Let’s get back to the Person class. Say we want to compare the instances of this class based on the age attribute (which makes sense, right?).
To do this, we’ll have to add a field, which we’ll call sort_index and set its value to the ageattribute’s value.
And the way we’d do this is by calling the __post_init__ method we saw in the previous example.
Now instances from the Person a class can be sorted with respect to the age attribute.
Dataclasses provides many features that allow you to easily work with classes that act as data containers.
In particular, this module helps to:
write less boilerplate code
represent objects in a readable format
implement custom ordering and comparisons
quickly access attributes and inspect them
use special methods such as __post_init__ to perform initialization of attributes that depend on others
define internal fields...
While learning about dataclasses, I went through many resources (blog posts, Youtube videos, PEP, the official python documentation)Here’s a curated list of the most interesting posts and videos I’ve found.
https://docs.python.org/3/library/dataclasses.html
https://realpython.com/python-data-classes/
https://dev.to/dbanty/you-should-use-python-dataclass-lkc
https://dev.to/isabelcmdcosta/dataclasses-in-python-are-nice-1fff
https://youtu.be/vBH6GRJ1REM
https://youtu.be/vRVVyl9uaZc
https://medium.com/mindorks/understanding-python-dataclasses-part-1-c3ccd4355c34
(french blog post) https://www.invivoo.com/dataclasses-python/
https://florimond.dev/en/posts/2018/10/reconciling-dataclasses-and-properties-in-python/
If you’ve made it this far, I really thank you for your time.
I hope I’ve shed some light on the different features that make dataclasses great and that I’ve convinced you to start using it.
If you’re still not convinced, feel free to tell me what alternatives you use. I’d really love to hear about them.
That’ll be all for me today. Until next time 👋