DEV Community

Victor Silva
Victor Silva

Posted on • Edited on

4

Make simple Python types rich

In the previous post of this series, I created a fictional example that uses a dict to map the installments of a loan to their corresponding due dates, in the following structure:

from datetime import date
due_dates = {
1: date(2018, 8, 20),
2: date(2018, 9, 20),
3: date(2018, 10, 20),
4: date(2018, 11, 20),
5: date(2018, 12, 20),
6: date(2019, 1, 20),
}
view raw devto0003-01.py hosted with ❤ by GitHub

Suppose that we pass that dict all over the place in our code, and that in some places we need to calculate the difference between the first and the last due date.
We could create a function that makes the calculation, and use it wherever we need it, right? And we could be nice to the callers by providing helpful type hints, of course:

from datetime import date, timedelta
from typing import Dict
def calculate_dates_delta(_due_dates: Dict[int, date]) -> timedelta:
min_date = min(_due_dates.values())
max_date = max(_due_dates.values())
return max_date - min_date
due_dates = {
1: date(2018, 8, 20),
2: date(2018, 9, 20),
3: date(2018, 10, 20),
4: date(2018, 11, 20),
5: date(2018, 12, 20),
6: date(2019, 1, 20),
}
delta = calculate_dates_delta(due_dates)
print(delta.days) # prints "153"
view raw devto0003-02.py hosted with ❤ by GitHub

But now we have to import that function in every place we deal with the dict and need the difference between the dates. It would be good if we could encapsulate both the function and the data in the same object without having to create a complex class.
Well, it turns out that we can do that by subclassing Python's built-in dict, and our subclass will behave like a standard dict because it will actually be one:

from datetime import date, timedelta
class InstallmentsDueDates(dict):
@property
def delta(self) -> timedelta:
min_date = min(self.values())
max_date = max(self.values())
return max_date - min_date
due_dates = InstallmentsDueDates({1: date(2018, 8, 20),
2: date(2018, 9, 20),
3: date(2018, 10, 20)})
due_dates[4] = date(2018, 11, 20)
due_dates.update({5: date(2018, 12, 20),
6: date(2019, 1, 20)})
print(due_dates[1].isoformat()) # prints 2018-08-20
print(due_dates.delta.days) # prints "153"
view raw devto0003-03.py hosted with ❤ by GitHub

Note that, unless we read the class's code, we have no clue that the keys and values are supposed to be integers and dates respectively. Not even PyCharm can help us:

We can fix that by inheriting from typing.Dict instead, which is a generic version of dict that can be annotated:

from datetime import date, timedelta
from typing import Dict
class InstallmentsDueDates(Dict[int, date]):
@property
def delta(self) -> timedelta:
min_date = min(self.values())
max_date = max(self.values())
return max_date - min_date
view raw devto0003-04.py hosted with ❤ by GitHub

Look at the autocomplete that we have now:

Sweet!

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay