Dictionary types in Python

Profile picture

Juan S. Díaz

· 4 min read

I could list a lot of reasons why Python type hinting feature1 is one of the most valuable features that this language has. Type hinting is the optional syntax to describe types in variables and functions using typing module. It’s available from Python 3.5 (or higher) without any additional package.

# from python 3.5 to 3.8
from typing import List
vowels: List[str] = ["a", "e", "i", "o", "u"]

Across newer Python versions, some types could be used without typing explicit import so the linters could give you more information about simpler way to use type hinting for your Python version:

# from python 3.9 to higher
vowels: list[str] = ["a", "e", "i", "o", "u"]

Anyway, defining a dictionary using basic type is too general and you could be interested in be more precise than this:

from typing import Any
user: dict[str, Any] = {
"name": "John Doe",
"email": "[email protected]",
"cash": 20.0,
"apps": ["app1", "app2", "app3"],
}

Keep in mind that Python typing will not force you or throw execution errors due to unexpected responses, but linters will warn you about incorrect access to attributes. We could talk about Python linters in another opportunity.

Advantages of a programming language with explicit types are the autocompletion and improved linting, very useful for giving context faster to other programmers and, obviously, AI tools for better suggestions.

Alternatives for dictionary typing

TypedDict

Check this example based on Harry Potter API. Let get_user() method returns a dict:

from typing import TypedDict
class Character(TypedDict):
id: str
name: str
alternate_names: list[str]
yearOfBirth: int
alive: bool
harry: Character = get_user(...)
harry["name"] # Harry Potter

Character metadata is bigger than example fields but you could define as many as you need. Also, you can add pydocs to explain fields.

I highly recommend this approach when you define types for third party API responses due to schema variability. Some APIs could include a Python package with its schema types but most of those integrations don’t do it.

Some considerations:

NamedTuple

from typing import NamedTuple, Any
class Character(NamedTuple):
id: str
name: str
alternate_names: list[str]
yearOfBirth: int
alive: bool
result: dict[str, Any] = get_user(...)
harry: Character = Character(
id=result["id"],
name=result["name"],
alternate_names=result["alternate_names"],
yearOfBirth=result["yearOfBirth"],
alive=result["alive"],
)
harry.name # Harry Potter

It’s a little more tricky than TypedDict due to longer value assignation but it returns an immutable object.

It’s a syntax sugar for collections.namedtuple and it’s very useful for application DTOs2 and any other object for read-only purposes. Also, dot notation is more strict to get execution errors.

Some considerations:

dataclasses

from typing import Any
from dataclasses import dataclass
@dataclass
class Character:
id: str
name: str
alternate_names: list[str]
yearOfBirth: int
alive: bool
result: dict[str, Any] = get_user(...)
harry: Character = Character(
id=result["id"],
name=result["name"],
alternate_names=result["alternate_names"],
yearOfBirth=result["yearOfBirth"],
alive=result["alive"],
)
harry.name # Harry Potter

If you want to aggregate behavior and properties all-in-one, you could use @dataclass decorator instead of previous ones.

This option actually adds methods to a class3 without changing its meaning like TypedDict and NamedTuple does. It could help a lot in frameworks and Python packages development and full object-oriented programming.

Some considerations:


Dataclasses could be immutable if you add frozen=True parameter to @dataclass decorator.

from dataclasses import dataclass
@dataclass(frozen=True)
class Character:
...

Conclusions

The following table could summary this article:

ComparisonTypedDictNamedTupledataclass
It behaves like…dicttupleclass
Recommended for…3rd party responsesRead-only data and DTOsFrameworks and packages using OOP
Is mutable?
Is extensible?

References

Footnotes

  1. It was added on PEP 484. You can get more info about Python typing future from PEP Index.

  2. Data Transfer Objects are object definitions for read-only and serializable data used by the processes into the application (like the database items). It’s a solution to standardize sent data of the processes.

  3. According official documentation, that decorator generates __init__ constructor based on attributes and it could include some basic comparison methods.