Python 方法将字典转换为命名元组或另一个类似字典的散列表?

我有一本这样的字典:

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

我想把它转换成一个命名元组。 我当前的方法是使用以下代码

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

产生了

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

这对我来说很好(我认为) ,但是我是否错过了一个内置的,如..。

nt = namedtuple.from_dict() ?

更新: 正如在评论中所讨论的,我想将我的字典转换为名称元组的原因是它变得可散列化,但仍然通常可用像一个结果。

更新2:4年后,我已经发布了这个问题,TLK 发布了一个新的答案推荐使用数据类装饰,我认为真的很棒。我想这就是我未来的用武之地。

62802 次浏览

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

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

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

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
from collections import namedtuple
nt = namedtuple('x', d.keys())(*d.values())
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.

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

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

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

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)