Backend Development 12 min read

How to Efficiently Map Dictionaries to Python Classes: From Namedtuple to Pydantic

Mapping external data such as MongoDB query results or API responses to Python class attributes can be done manually, but this article explores several automated approaches—including namedtuple, setattr, a custom ModelBase/Type mapper, and Pydantic—detailing implementation, error handling, and performance benchmarks to guide developers in choosing the optimal solution.

Code Mala Tang
Code Mala Tang
Code Mala Tang
How to Efficiently Map Dictionaries to Python Classes: From Namedtuple to Pydantic

When we need to map dictionaries from external sources (e.g., MongoDB query results or API responses) to class attributes, manual mapping is time‑consuming and error‑prone. This article presents several automated approaches and compares their performance.

Existing Methods

Example dictionary:

<code>_dictionary = {
    'name': 'Bob',
    'age': 23,
    'title': 'SSE',
    'department': 'SWE'
}</code>

Using Namedtuples

<code>from collections import namedtuple

_employee = {
    'name': 'Bob',
    'age': 23,
    'title': 'SSE',
    'department': 'SWE'
}
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department')
e_record = EmployeeRecord._make(_employee.values())
print(e_record.name, e_record.age)
</code>

Namedtuple requires the dictionary to contain all defined fields; missing or extra keys raise TypeError .

Using setattr

<code>class EmployeeRecord:
    def __init__(self, d=None):
        if d is not None:
            for key, value in d.items():
                setattr(self, key, value)

_employee = {
    'name': 'Danushka',
    'age': 23,
    'title': 'SSE',
    'department': 'SWE'
}
o = EmployeeRecord(_employee)
print(o.name)
</code>

This method creates attributes dynamically but lacks defaults and type safety; accessing a missing attribute raises AttributeError .

Custom Class Mapper

To overcome the limitations of namedtuple and setattr , a custom mapper using two core classes— Type and ModelBase —is introduced.

Type Class

<code>class Type:
    alias: str
    default_factory: callable

    def __init__(self, alias: str = None, default_factory: callable = None) -> None:
        self.alias = alias
        self.default_factory = default_factory
</code>

Type holds metadata for class attributes, allowing an alias for a different dictionary key and a callable default_factory for value transformation.

ModelBase Class

<code>class ModelBase:
    def __init__(self, payload):
        for key, _class in self.__annotations__.items():
            _attr_key_in_payload = key
            _attr_mapper = None
            if hasattr(self, key) and type(self.__getattribute__(key)) is Type:
                attr: Type = self.__getattribute__(key)
                if attr.alias:
                    _attr_key_in_payload = attr.alias
                if attr.default_factory and callable(attr.default_factory):
                    _attr_mapper = attr.default_factory
            try:
                if _attr_key_in_payload in payload:
                    if getattr(_class, '_name', None) == 'List' and len(getattr(_class, '__args__', [])):
                        _list = [_class.__args__[0](e) for e in payload[_attr_key_in_payload]]
                        self.__setattr__(key, _list)
                    else:
                        self.__setattr__(key, _class(payload[_attr_key_in_payload]))
                if _attr_mapper:
                    self.__setattr__(key, _attr_mapper(self.__getattribute__(key)))
                else:
                    self.__setattr__(key, None)
            except Exception as e:
                print(f"Error setting attribute {key}: {e}")
                self.__setattr__(key, None)
</code>

ModelBase iterates over annotated attributes, applies aliases, default factories, handles list types, and safely assigns values, falling back to None on errors.

Example Employee Class

<code>class Employee(ModelBase):
    id: str = Type(alias='_id')
    name: str
    age: int
    title: str
    department: str
    image_url: str = Type(alias='image', default_factory=PathMapper('image/employee').map_url)
</code>

Creating an Employee instance with a dictionary automatically maps keys (including aliases) and applies the URL‑building factory for image_url .

Using Pydantic

Pydantic leverages Python type hints for data validation and management.

<code>pip install pydantic</code>
<code>from typing import List
from pydantic import BaseModel, Field, field_validator
from .path_mapper import PathMapper

class Employee(BaseModel):
    id: str = Field(alias='_id')
    name: str
    age: int
    title: str
    department: List[str]
    image_url: str = Field(alias='image')

    @field_validator('image_url', mode='before')
    @classmethod
    def to_image_url(cls, raw: str) -> str:
        return PathMapper('image/employee').to_url(raw)
</code>

Instantiating the model with a dictionary validates types, applies alias mapping, and runs the custom validator for image_url .

Performance Comparison

Benchmarks were run on up to 100,000 dictionary objects for four methods: custom class mapper, Pydantic, namedtuple, and setattr . The results show that setattr is the fastest, namedtuple is stable, Pydantic offers the most validation, and the custom mapper balances performance with flexibility.

Performance test results
Performance test results

Overall, while Pydantic excels at data validation, the custom class approach provides a versatile solution that balances speed and functionality, making it suitable for applications requiring reliable performance and complex data handling.

PythonPerformance BenchmarkData mappingNamedtuplePydantic
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.