How to Serialize Python namedtuple to YAML
This tutorial will guide you through converting namedtuple instances to YAML format and back again.
You’ll learn how to overcome the default limitations of PyYAML when dealing with namedtuples, create custom representers and constructors, and handle various special cases.
Default Behavior of PyYAML with namedtuple
By default, PyYAML doesn’t recognize namedtuple
objects and treats them like regular tuples.
This means that important information like field names is lost during serialization.
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age']) amira = Person('Amira', 28) print(yaml.dump(amira))
Output:
!!python/object:__main__.Person - Amira - 28
It doesn’t preserve the field names and uses a Python-specific tag.
Custom YAML representers for namedtuple
You can create a custom representer to solve that problem:
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age']) def represent_namedtuple(dumper, data): return dumper.represent_mapping('tag:yaml.org,2002:map', data._asdict().items()) yaml.add_representer(Person, represent_namedtuple) amira = Person('Amira', 28) print(yaml.dump(amira))
Output:
name: Amira age: 28
This custom representer converts the namedtuple to a dictionary.
Serialization methods
Convert namedtuple to dictionary
You can convert namedtuples to dictionaries before serialization:
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age']) amira = Person('Amira', 28) print(yaml.dump(amira._asdict()))
Output:
age: 28 name: Amira
This method is simple but doesn’t preserve the order of fields.
Preserve field names and order
To maintain field order, use an OrderedDict:
from collections import namedtuple, OrderedDict import yaml Person = namedtuple('Person', ['name', 'age']) def represent_ordereddict(dumper, data): return dumper.represent_mapping('tag:yaml.org,2002:map', data.items()) yaml.add_representer(OrderedDict, represent_ordereddict) amira = Person('Amira', 28) print(yaml.dump(OrderedDict(amira._asdict())))
Output:
name: Amira age: 28
This method preserves the field order in the YAML output.
Handle nested namedtuples
For nested namedtuples, create a recursive function:
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age']) Address = namedtuple('Address', ['street', 'city', 'resident']) def namedtuple_to_dict(obj): if isinstance(obj, tuple) and hasattr(obj, '_asdict'): return {key: namedtuple_to_dict(value) for key, value in obj._asdict().items()} elif isinstance(obj, (list, tuple)): return [namedtuple_to_dict(item) for item in obj] else: return obj address = Address('123 Nile St', 'Cairo', Person('Amira', 28)) print(yaml.dump(namedtuple_to_dict(address)))
Output:
city: Cairo resident: age: 28 name: Amira street: 123 Nile St
Deserialization methods
Custom YAML constructors for namedtuple
To deserialize YAML back into namedtuples, create custom constructors:
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age']) def construct_yaml_person(loader, node): value = loader.construct_mapping(node) return Person(**value) class CustomLoader(yaml.SafeLoader): pass CustomLoader.add_constructor('!Person', construct_yaml_person) yaml_string = """ !Person name: Amira age: 28 """ print(yaml.load(yaml_string, Loader=CustomLoader))
Output:
Person(name='Amira', age=28)
This custom constructor allows you to recreate namedtuple objects from YAML data.
Recreate namedtuple from YAML data
You can also create a function to recreate namedtuples:
from collections import namedtuple import yaml def dict_to_namedtuple(d): for key, value in d.items(): if isinstance(value, dict): d[key] = dict_to_namedtuple(value) return namedtuple('GenericNamedTuple', d.keys())(**d) yaml_string = """ name: Amira age: 28 address: street: 123 Nile St city: Cairo """ data = yaml.safe_load(yaml_string) person = dict_to_namedtuple(data) print(person) print(person.address.street)
Output:
GenericNamedTuple(name='Amira', age=28, address=GenericNamedTuple(street='123 Nile St', city='Cairo')) 123 Nile St
This function recursively converts dictionaries to namedtuples.
Deal with default values in namedtuples
To handle namedtuples with default values, modify your serialization method by omitting fields with default values from the YAML output:
from collections import namedtuple import yaml Person = namedtuple('Person', ['name', 'age', 'city'], defaults=['Unknown']) def represent_namedtuple(dumper, data): return dumper.represent_mapping('tag:yaml.org,2002:map', {k: v for k, v in data._asdict().items() if v != data._field_defaults.get(k)}) yaml.add_representer(Person, represent_namedtuple) amira = Person('Amira', 28) print(yaml.dump(amira))
Output:
age: 28 name: Amira
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.