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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43921240/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 23:31:46  来源:igfitidea点击:

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

pythondictionarynamedtuple

提问by Max Power

I have a dictionary like:

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

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

which produces

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

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...

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.

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.

回答by wim

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

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

MyTuple = namedtuple('MyTuple', sorted(d))

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

Now to create instances from this dict, or other dicts 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. This differs from dictequality comparisons, which do take into account the keys, and it may not be what you wanted nor expected from the namedtupletype!

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. This differs from dictequality comparisons, which do take into account the keys, and it may not be what you wanted nor expected from the namedtupletype!

If you only have one dict, rather than a bunch of dicts sharing the same set of keys, then there is no point to create this namedtuple in the first place. You should just use a namespace objectinstead:

If you only have one dict, rather than a bunch of dicts sharing the same set of keys, then there is no point to create this namedtuple in the first place. You should just use a namespace objectinstead:

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

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

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

回答by Mitendra

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

回答by fuggy_yama

You can use this function to handle nested dictionaries:

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

回答by Ritesh Dubey

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

Access as obj.name.firstName, obj.id

This will work for nested dictionary with any data types.

This will work for nested dictionary with any data types.

回答by VisioN

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

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:

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'))

回答by Rodrigo E. Principe

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

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

回答by Mikaelblomkvistsson

Check this out:

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.

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

Test with py.test:

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