Pythonic way to convert a dictionary into namedtuple or another hashable dict-like?

PythonDictionaryNamedtuple

Python Problem Overview


I have a dictionary like:

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

which I would like to convert to a namedtuple. My current approach is with the following code

namedTupleConstructor = namedtuple('myNamedTuple', ' '.join(sorted(d.keys())))
nt= namedTupleConstructor(**d)

which produces

> myNamedTuple(a=1, b=2, c=3, d=4)

This works fine for me (I think), but am I missing a built-in such as...

nt = namedtuple.from_dict() ?

UPDATE: as discussed in the comments, my reason for wanting to convert my dictionary to a namedtuple is so that it becomes hashable, but still generally useable like a dict.

UPDATE2: 4 years after I've posted this question, TLK posts a new answer recommending using the dataclass decorator that I think is really great. I think that's now what I would use going forward.

Python Solutions


Solution 1 - Python

To create the subclass, you may just pass the keys of a dict directly:

MyTuple = namedtuple('MyTuple', d)

Now to create tuple instances from this dict, or any other dict with matching keys:

my_tuple = MyTuple(**d)

Beware: namedtuples compare on values only (ordered). They are designed to be a drop-in replacement for regular tuples, with named attribute access as an added feature. The field names will not be considered when making equality comparisons. It may not be what you wanted nor expected from the namedtuple type! This differs from dict equality comparisons, which do take into account the keys and also compare order agnostic.

For readers who don't really need a type which is a subclass of tuple, there probably isn't much point to use a namedtuple in the first place. If you just want to use attribute access syntax on fields, it would be simpler and easier to create namespace objects instead:

>>> from types import SimpleNamespace
>>> SimpleNamespace(**d)
namespace(a=1, b=2, c=3, d=4)

> my reason for wanting to convert my dictionary to a namedtuple is so that it becomes hashable, but still generally useable like a dict

For a hashable "attrdict" like recipe, check out a frozen box:

>>> from box import Box
>>> b = Box(d, frozen_box=True)
>>> hash(b)
7686694140185755210
>>> b.a
1
>>> b["a"]
1
>>> b["a"] = 2
BoxError: Box is frozen

There may also be a frozen mapping type coming in a later version of Python, watch this draft PEP for acceptance or rejection:

PEP 603 -- Adding a frozenmap type to collections

Solution 2 - Python

from collections import namedtuple
nt = namedtuple('x', d.keys())(*d.values())

Solution 3 - Python

I'd like to recommend the dataclass for this type of situation. Similar to a namedtuple, but with more flexibility.

https://docs.python.org/3/library/dataclasses.html

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Solution 4 - Python

If you want an easier approach, and you have the flexibility to use another approach other than namedtuple I would like to suggest using SimpleNamespace (docs).

from types import SimpleNamespace as sn

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
dd= sn(**d)
# dd.a>>1

# add new property
dd.s = 5
#dd.s>>5

PS: SimpleNamespace is a type, not a class

Solution 5 - Python

You can use this function to handle nested dictionaries:

def create_namedtuple_from_dict(obj):
	if isinstance(obj, dict):
		fields = sorted(obj.keys())
		namedtuple_type = namedtuple(
			typename='GenericObject',
			field_names=fields,
			rename=True,
		)
		field_value_pairs = OrderedDict(
			(str(field), create_namedtuple_from_dict(obj[field]))
			for field in fields
		)
		try:
			return namedtuple_type(**field_value_pairs)
		except TypeError:
			# Cannot create namedtuple instance so fallback to dict (invalid attribute names)
			return dict(**field_value_pairs)
	elif isinstance(obj, (list, set, tuple, frozenset)):
		return [create_namedtuple_from_dict(item) for item in obj]
	else:
		return obj

Solution 6 - Python

def toNametuple(dict_data):
    return namedtuple(
        "X", dict_data.keys()
    )(*tuple(map(lambda x: x if not isinstance(x, dict) else toNametuple(x), dict_data.values())))
    
d = {
    'id': 1,
    'name': {'firstName': 'Ritesh', 'lastName':'Dubey'},
    'list_data': [1, 2],
}

obj = toNametuple(d)

Access as obj.name.firstName, obj.id

This will work for nested dictionary with any data types.

Solution 7 - Python

I find the following 4-liner the most beautiful. It supports nested dictionaries as well.

def dict_to_namedtuple(typename, data):
    return namedtuple(typename, data.keys())(
        *(dict_to_namedtuple(typename + '_' + k, v) if isinstance(v, dict) else v for k, v in data.items())
    )

The output will look good also:

>>> nt = dict_to_namedtuple('config', {
...     'path': '/app',
...     'debug': {'level': 'error', 'stream': 'stdout'}
... })

>>> print(nt)
config(path='/app', debug=config_debug(level='error', stream='stdout'))

Solution 8 - Python

use the dictionary keys as the fieldnames to the namedtuple

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

def dict_to_namedtuple(d):
    return namedtuple('GenericDict', d.keys())(**d)

 result=dict_to_namedtuple(d)
 print(result)

output

  GenericDict(a=1, b=2, c=3, d=4)

Solution 9 - Python

Check this out:

def fill_tuple(NamedTupleType, container):
    if container is None:
        args = [None] * len(NamedTupleType._fields)
        return NamedTupleType(*args)
    if isinstance(container, (list, tuple)):
        return NamedTupleType(*container)
    elif isinstance(container, dict):
        return NamedTupleType(**container)
    else:
        raise TypeError("Cannot create '{}' tuple out of {} ({}).".format(NamedTupleType.__name__, type(container).__name__, container))

Exceptions for incorrect names or invalid argument count is handled by __init__ of namedtuple.

Test with py.test:

def test_fill_tuple():
    A = namedtuple("A", "aa, bb, cc")

    assert fill_tuple(A, None) == A(aa=None, bb=None, cc=None)
    assert fill_tuple(A, [None, None, None]) == A(aa=None, bb=None, cc=None)
    assert fill_tuple(A, [1, 2, 3]) == A(aa=1, bb=2, cc=3)
    assert fill_tuple(A, dict(aa=1, bb=2, cc=3)) == A(aa=1, bb=2, cc=3)
    with pytest.raises(TypeError) as e:
        fill_tuple(A, 2)
    assert e.value.message == "Cannot create 'A' tuple out of int (2)."

Solution 10 - Python

Although I like @fuggy_yama answer, before read it I got my own function, so I leave it here just to show a different approach. It also handles nested namedtuples

def dict2namedtuple(thedict, name):
    
    thenametuple = namedtuple(name, [])
    
    for key, val in thedict.items():
        if not isinstance(key, str):
            msg = 'dict keys must be strings not {}'
            raise ValueError(msg.format(key.__class__))
            
        if not isinstance(val, dict):
            setattr(thenametuple, key, val)
        else:
            newname = dict2namedtuple(val, key)
            setattr(thenametuple, key, newname)
    
    return thenametuple

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMax PowerView Question on Stackoverflow
Solution 1 - PythonwimView Answer on Stackoverflow
Solution 2 - PythonMitendraView Answer on Stackoverflow
Solution 3 - PythonTLK3View Answer on Stackoverflow
Solution 4 - Pythontoing_toingView Answer on Stackoverflow
Solution 5 - Pythonfuggy_yamaView Answer on Stackoverflow
Solution 6 - PythonRitesh DubeyView Answer on Stackoverflow
Solution 7 - PythonVisioNView Answer on Stackoverflow
Solution 8 - PythonGolden LionView Answer on Stackoverflow
Solution 9 - PythonMikaelblomkvistssonView Answer on Stackoverflow
Solution 10 - PythonRodrigo E. PrincipeView Answer on Stackoverflow