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

Question:

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.

Asked By: Max Power

||

Answers:

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

Answered By: wim

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)."
Answered By: Mikaelblomkvistsson

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
Answered By: fuggy_yama

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
Answered By: Rodrigo E. Principe
from collections import namedtuple
nt = namedtuple('x', d.keys())(*d.values())
Answered By: Mitendra
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.

Answered By: Ritesh Dubey

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'))
Answered By: VisioN

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

Answered By: toing_toing

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
Answered By: TLK3

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)
Answered By: Golden Lion
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.