107

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.

Max Power
  • 8,265
  • 13
  • 50
  • 91
  • With namedtuples, you're supposed to create the namedtuple type once and use it repeatedly, not generate a new namedtuple type every time. Generating a new namedtuple type every time is slow and defeats any space benefit. – user2357112 May 11 '17 at 16:45
  • 1
    @user2357112 presumably, the user has many dicts with the same keys. – wim May 11 '17 at 16:47
  • 2
    There's not going to be a built-in that constructs the type and the tuple at the same time, because you're supposed to reuse the type. – user2357112 May 11 '17 at 16:47
  • For going the other way (namedtuple into dictionary), look here: https://stackoverflow.com/q/26180528/674039 – wim Oct 01 '20 at 16:07

10 Answers10

170

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

from collections import namedtuple

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

Dan Ciborowski - MSFT
  • 6,807
  • 10
  • 53
  • 88
wim
  • 338,267
  • 99
  • 616
  • 750
  • 8
    For the one-liner, you need: MyNamedTuple = namedtuple('MyNamedTuple', d.keys())(**d) – FLab May 11 '17 at 16:47
  • Interesting, is a Namespace hashable? That was my original reason for wanting to convert a dict to a namedtuple – Max Power May 11 '17 at 16:51
  • 3
    @MaxPower: Are you aware that the namedtuples you construct from `{'a': 1}` and `{'b': 1}` are going to be equal, and have equal hash codes? Something like `tuple(sorted(d.items()))` or `frozenset(d.items())` may be more appropriate. They'll also handle keys that aren't valid Python identifiers, like `'for'` or `3`. – user2357112 May 11 '17 at 16:54
  • @user2357112 I was not aware of that, thanks for the info. So {'a':1} and {'b':1} would become different tuples (tuples with different hashes) but they would become equal namedtuples? – Max Power May 11 '17 at 17:03
  • 1
    @MaxPower: `tuple(sorted(d.items()))` would construct different tuples, because it includes the keys in the actual tuples. (Note that it requires the keys to be sortable, which is fine for strings, and which you're already relying on. The `frozenset` thing would handle unorderable keys.) The namedtuples you're constructing don't include the keys in the tuples themselves. – user2357112 May 11 '17 at 17:06
  • Just to expand on @FLab's comment--if you're using 3.5+, using the `namedtuple` constructor creates a new subclass every time; you take a tremendous performance hit over defining a subclass first with `typing.NamedTuple` and then doing `MyTuple(**d)` if you're doing it many times – stuart May 24 '17 at 19:12
  • 1
    why "should" use SimpleNamespace instead of namedtuple if there is only 1 dict? – matt wilkie Sep 25 '19 at 03:26
  • Does this work with [`typing.NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple) as well? – thinwybk Sep 29 '20 at 09:13
  • @thinwybk Not really, no. `namedtuple` is meant to be called (factory function) but `NamedTuple` is meant to be subclassed. – wim Oct 01 '20 at 16:20
  • @wim Do you mean the code example above `from types import SimpleNamespace`? – thinwybk Oct 02 '20 at 17:08
  • `SimpleNamespace` instances are not hashable, so they're not an appropriate substitute for namedtuples if you actually want a hashable record type (as the OP wanted) – wim Oct 02 '20 at 17:38
18
from collections import namedtuple
nt = namedtuple('x', d.keys())(*d.values())
Mitendra
  • 1,426
  • 17
  • 11
8

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

toing_toing
  • 2,334
  • 1
  • 37
  • 79
7

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
T3metrics
  • 135
  • 2
  • 11
  • 1
    hey thanks for adding this, I really like this answer. Will add an update to my question linking to it so people see this going forward. – Max Power Aug 27 '21 at 14:42
  • fantastic answer. note that dataclasses has an `asdict` helper function to ensure that dataclass instances can be serialized as needed. – rv.kvetch Oct 01 '21 at 17:20
  • for a more complex use case - for ex. you want key remapping on deserialization, need to work with nested dataclasses, or exclude default values on serialization, I'd suggest a fast serialization library like the [dataclass-wizard](https://dataclass-wizard.readthedocs.io/). – rv.kvetch Oct 01 '21 at 17:23
  • 1
    I've found dataclasses have a lot of quirks and can be difficult to get exactly what you want. While also not perfect, may want to consider the Pydantic package - it has both a custom class that works a lot like dataclasses and a version of it that is directly compatible with dataclasses, but some of the quirks are worked out. It does require a bit of change in direction of thinking about classes in some ways though... – LightCC Jun 23 '22 at 23:33
  • I downvoted because while dataclasses are great, this does not address the original question about getting a named tuple -like structure for any arbitrary dict. – bfontaine Jan 19 '23 at 14:33
4

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
fuggy_yama
  • 607
  • 6
  • 24
2

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)
Golden Lion
  • 3,840
  • 2
  • 26
  • 35
1
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.

Smart Manoj
  • 5,230
  • 4
  • 34
  • 59
1

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'))
VisioN
  • 143,310
  • 32
  • 282
  • 281
-2

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)."
-2

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
Rodrigo E. Principe
  • 1,281
  • 16
  • 26