Dictionaries are rarely the solution

What do I mean by this? Well, the data structure under the hood (Hash Table) is super important and integral to a fast and functional application. However, if we’re only using dictionaries then we’re leaving a lot up to the imagination of the next developer reading the code.

Wait, hold up... why are you talking about the next developer?

As developers most of our time is spent reading, not writing code. Therefore, most of the code you read will have been written by someone else. So, do you want read well documented, rigorously tested, and highly transparent code? Yes... you do. So, you should write code the same way.

Okay, fine. But what does this have to do with dicts?

Well, there is no transparency into dicts. Consider the example

from typing import List, Dict

alice = {"age": 7, "name": "Alice", "friends": ["Hatter", "Cheshire"]}

wonderland: List[Dict] = []
wonderland.append(alice)

We just created a dictionary representation of Alice and threw her in Wonderland. If you’re walking through wonderland and saw a Dict, do we know anything about them? Do they have a name, maybe an alias... or perhaps an age? We could improve this slightly by adding some more type hints

from typing import List, Dict, Union

alice = {"age": 19, "name": "Alice", "friends": ["Hatter", "Cheshire"]}

wonderland: List[Dict[str, Union[str, int, List[str]]]] = []
wonderland.append(alice)

https://giphy.com/gifs/theoffice-zCpYQh5YVhdI1rVYpE

Wow, that’s gross! But, helpful? Maybe? No... this is terrible. To quote my favorite python coach: There must be a better way!

Use a class!

Python classes are essentially closures with some magic methods for convenience.

class Alice(object):
    def __init__(self, age: int, name: str, friends: List[str]):
        self.age = age
        self.name = name
        self.friends = friends

class_method_count = len(dir(Alice)) #=> 26

Now, this is the most basic form of a class. This is useful, but not that useful. A lot of work has been done over the years to improve class functionality. One of the more recent ones being Python’s dataclasses. This library adds a ton of boilerplate magic methods to our classes.

Okay, let’s look!

from dataclasses import dataclass
from typing import List

class BasicAlice(object):
    def __init__(self, age: int, name: str, friends: List[str]):
        self.age = age
        self.name = name
        self.friends = friends

@dataclass
class DataAlice:
    age: int
    name: str
    friends: List[str]

basic = BasicAlice(9, "Alice", ["Hatter"])
data = DataAlice(9, "Alice", ["Hatter"])

print(basic) #=> <__main__.BasicAlice object at 0x10e2f6b20>
print(data)  #=> DataAlice(age=9, name='Alice', friends=['Hatter'])

print(len(dir(basic))) #=> 29
print(len(dir(data))) #=> 32

Okay, well, there’s an immediate bonus! Less code, more visibility, and more functionality!

Python’s dataclasses library is amazing!

Are you familiar with how the python interpreter handles Default Mutable Arguments? Well, if not, go give that a read right now and then come back here and finish reading this article! It might take 5 minutes to learn what the problem is. (Go give it a read anyway! The author is super cool 😎)