Skip to content

#257: Less Boilerplate Code With Data Classes

Creating DTOs with NamedTuple was a nice way to create read-only structures to pass between functions. But not always is a read-only structure the right tool for the job. Let us look how we can create changeable structures like classes with ease.

The problem

Writing classes in Python requires a lot of recuring boilerplate code:

1
2
3
4
5
class Book:
    def __init__(self, title: str, author: str, pages: int):
        self.title = title
        self.author = author
        self.pages = pages
>>> quick = Book("The Quick Python Book", "Naomi Ceder", 550)
>>> quick
<__main__.Book object at 0x00000246BBD42990>

Yes, we can write classes that way and many developers do it without a second thought. But I do not like it and need a nicer approach that is more like Pydantic models. Luckily for me, there is such an approach directly built into Python.

Data classes

Data classes are part of Python since version 3.7 and reduce the boilerplate code by a lot. If we use the @dataclass decorator, we can write the class from above with just these lines:

1
2
3
4
5
6
7
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int

I like this approach a lot better. Not only do we skip the __init__ method, we get most of the helpful special methods for free and with useful implementations:

>>> quick = Book("The Quick Python Book", "Naomi Ceder", 550)
>>> quick
Book(title='The Quick Python Book', author='Naomi Ceder', pages=550)

If we want to add our own methods, we can do that as we would with regular classes:

1
2
3
4
5
6
7
8
@dataclass
class Book:
    title: str
    author: str
    pages: int

    def csv(self):
        return f"{self.title}, {self.author}, {self.pages}"
>>> quick = Book("The Quick Python Book", "Naomi Ceder", 550)
>>> quick.csv()
'The Quick Python Book, Naomi Ceder, 550'

Default values

If we want to set default values, we can do so in our property list:

1
2
3
4
@dataclass
class Item:
    name: str
    value: int = 1

We get now a 1 as value even when we do not set it:

>>> tools = Item("hammer")
>>> tools
Item(name='hammer', value=1)

Yet we can still set a value if we want to:

>>> books = Item("Python", 2)
>>> books
Item(name='Python', value=2)

Unchangeable data classes

If we want to have classes where we cannot change the values, we do not need to go back to named tuples. Instead, we can pass frozen=True to the decorator:

1
2
3
4
@dataclass(frozen=True)
class Point:
    x: int
    y: int

If we now try to change the values, we get a FrozenInstanceError:

>>> start = Point(2,4)
>>> start
Point(x=2, y=4)
>>> start.x = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'x'

Next

With the @dataclasses decorator we can save a lot of boilerplate code and still get the whole flexibility of classes in Python. I find this a massive improvement that I will use a lot.

Next week we work a bit with XML and fix the RSS feed of this blog.