So far, we have seen how dictionaries can be used as a basic mutable data structure with keys and values. Dictionaries can also be used as records, where each key represents a field in a record, and the corresponding value represents the value of that field. This is similar to how dataclasses work in Python, where each attribute represents a field in a data structure.
person = {
'name': 'Ada',
'age': 33,
'city': 'Newark'
}
print(person)
# Output: {'name': 'Ada', 'age': 33, 'city': 'Newark'}
One of the advantages of using dictionaries as records is that the values do not have to be of the same type. In the example shown here, we define a dictionary representing a Person record with three fields: name, age, and city. The name field is a string, the age field is an integer, and the city field is a string. This flexibility allows us to represent complex data structures using dictionaries.
Unfortunately, this does make it harder to enforce a consistent structure across multiple records. Dataclasses provide a way to define a consistent structure for data records. The advantage of using dictionaries is that we can easily create and manipulate records without the need to define a class, and we can add or remove fields as needed.
person = {
'name': 'Ada',
'age': 33,
'city': 'Newark'
}
def print_person(person: dict):
print(f"Name: {person['name']}")
print(f"Age: {person['age']}")
print(f"City: {person['city']}")
print_person(person)
What type of dictionary is being passed into the print_person function? We cannot simply say dict[str, str], because one of the fields is an integer.
A more accurate type would be dict[str, str | int], which indicates that the values can be either strings or integers.
However, when iterating over a key and value pair, the type of the value is either a string or an integer, not both, and we have to be careful what operations we would work with.
There are sophisticated ways of handling this, but for now, we will keep it simple and avoid mixing our types in a single dictionary.
The only exception will be to convert a dictionary to a dataclass, and vice versa.
asdict function
from dataclasses import dataclass, asdict
@dataclass
class Person:
name: str
age: int
city: str
person = Person("Ada", 33, "Newark")
person_dict = asdict(person)
print(person_dict)
# Output: {'name': 'Ada', 'age': 33, 'city': 'Newark'}
Since dataclasses and dictionaries share a similar structure, we can convert a dataclass instance to a dictionary. The dataclasses module provides an asdict function. This function converts the dataclass instance to a dictionary where the keys are the field names and the values are the corresponding values of the instance.
In the example shown here, we define a Person dataclass with three fields: name, age, and city. We then create an instance of the Person class with the values "Ada", 33, and "Newark". The resulting dictionary contains the field names as keys and the corresponding values as values.
from dataclasses import dataclass, asdict
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class Person:
name: str
age: int
address: Address
address = Address("123 Main St", "Newark", "19711")
person = Person("Ada", 33, address)
person_dict = asdict(person)
print(person_dict)
The asdict function can handle nested dataclasses as well. In the example shown here, we define two dataclasses: Address and Person. The Person dataclass has a field address that is an instance of the Address dataclass. When we convert the Person instance to a dictionary, the resulting dictionary will contain the nested dataclass instance as another dictionary.
The asdict function handles the conversion of nested dataclasses to dictionaries recursively, ensuring that the resulting dictionary contains all the nested data as dictionaries.
The function also works for dictionaries that contain other dictionaries, lists, or other complex data structures.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
city: str
person_dict = {'name': 'Ada', 'age': 33, 'city': 'Newark'}
person = Person(**person_dict)
print(person)
# Output: Person(name='Ada', age=33, city='Newark')
We can also convert dictionaries to dataclasses using dictionary unpacking. In the example shown here, we define a Person dataclass with three fields: name, age, and city. We then create a dictionary with the field names as keys and the corresponding values as values. By using the ** operator to unpack the dictionary into the dataclass constructor, we can create an instance of the Person class with the values from the dictionary.
from dataclasses import dataclass
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class Person:
name: str
age: int
address: Address
person_dict = {
'name': 'Ada',
'age': 33,
'address': {
'street': '123 Main St',
'city': 'Newark',
'zip_code': '19711'
}
}
person = Person(**person_dict)
print(person)
print(type(person.address))
# Output: <class 'dict'>
Unfortunately, when using dictionary unpacking to create nested dataclasses, the nested dictionaries are not automatically converted to dataclass instances. In the example shown here, we define two dataclasses: Address and Person. We create a dictionary with the field names as keys and the corresponding values as values. The address field in the dictionary is another dictionary representing the Address dataclass. When we unpack the dictionary into the Person constructor, the address field remains a dictionary, not an instance of the Address dataclass.
In order to convert nested dictionaries to nested dataclass instances, we need to manually convert the nested dictionaries to dataclass instances before creating the parent dataclass instance.
from dataclasses import dataclass
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class Person:
name: str
age: int
address: Address
def dict_to_person(person_dict: dict) -> Person:
return Person(
name=person_dict['name'],
age=person_dict['age'],
address=Address(**person_dict['address'])
)
person_dict = {
'name': 'Ada',
'age': 33,
'address': {
'street': '123 Main St',
'city': 'Newark',
'zip_code': '19711'
}
}
print(dict_to_person(person_dict))
To correctly handle nested dictionaries and convert them to nested dataclass instances, we can define a function that takes a dictionary and returns an instance of the parent dataclass with the nested dataclass instances correctly converted. In the example shown here, we define a dict_to_person function that takes a dictionary representing a Person instance and returns a Person instance with the address field converted to an instance of the Address dataclass.
By manually converting the nested dictionaries to nested dataclass instances, we can ensure that the resulting dataclass instances contain the correct data and maintain the structure of the dataclasses.
asdict function in the dataclasses module can convert dataclass instances to dictionaries, and vice versa. This function even works recursively for nested dataclasses.