简体   繁体   English

如何将嵌套的 python 字典转换为简单的命名空间?

[英]How to convert a nested python dictionary into a simple namespace?

Let's say I have a nested dictionary with depth N. How can I convert each inner nested dictionary into a simple namespace?假设我有一个深度为 N 的嵌套字典。如何将每个内部嵌套字典转换为一个简单的命名空间?

example_input = {key0a: "test", key0b: {key1a: {key2a: {...keyNx}, key2b: "test"} ,key1b: "test"}}

example_output = SimpleNamespace(key0a: "test", key0b: SimpleNamespace(key1a: SimpleNamespace(key2a: SimpleNamespace(...keyNx), key2b: "test"), key1b: "test"))

Are there better alternatives to make the keys of the dictionary accessible per dot notation (eg example_input.key0a) if the example_input dict is given - without having external dependencies?如果给出了 example_input 字典,是否有更好的替代方法可以使字典的键可以按点表示法访问(例如 example_input.key0a) - 没有外部依赖项?

Just answering your second (last) question - it is popular topic, there exist many different projects (non-standard) that make your dictionary into dot notation object.只是回答你的第二个(最后一个)问题 - 这是一个热门话题,存在许多不同的项目(非标准),可以让你的字典变成点符号 object。

For example this one - attrdict .例如这个 one- attrdict Install it through pip install attrdict .通过pip install attrdict安装它。

Example of usage:使用示例:

Try it online!在线尝试!

from attrdict import AttrDict
d = {'a': 1, 'b': [{'c': 2}, {'d': {'e': {'f': {5: {'g': 3}}}}}]}
ad = AttrDict(d)
print(ad.b[1].d.e.f(5).g) # 3

If you wonder how module like attrdict is implemented, then I wrote a very simple implementation of similar functionality (of course real attrdict should be more rich):如果你想知道attrdict 之类的模块是如何实现的,那么我写了一个非常简单的类似功能的实现(当然真正的attrdict应该更丰富):

Try it online! 在线尝试!

class AttrD(object):
    def __init__(self, d = {}):
        self.set_d(d)
    def __getattr__(self, key):
        return AttrD(self.get_or_create(key))
    def __setattr__(self, key, value):
        self.set_or_create(key, value)
    def __getitem__(self, key):
        return AttrD(self.get_or_create(key))
    def __setitem__(self, key, value):
        self.set_or_create(key, value)
    def __call__(self, key):
        return AttrD(self.get_or_create(key))
    def __repr__(self):
        return repr(self._d)
    def to_obj(self):
        return self._d
    def set_d(self, d):
        super(AttrD, self).__setattr__('_d', d)
    def get_or_create(self, name):
        if type(self._d) in (dict,) and name not in self._d:
            self._d[name] = {}
        if type(self._d) in (list, tuple) and len(self._d) <= name:
            self.set_d(self._d + type(self._d)(
                [None] * (name + 1 - len(self._d))))
        return self._d[name]
    def set_or_create(self, key, value):
        self.get_or_create(key)
        self._d[key] = value

ad = AttrD({'a': 1, 'b': [{'c': 2}, {'d': {'e': {'f': {5: {'g': 3}}}}}]})
ad.b[1].d.e.f(5).g = [4, 5, 6]
print(ad.b[1].d.e.f(5).g[2]) # 6
print(AttrD({'a': 123}).b.c) # Non-existent defaults to {}

Using recursion使用递归

example_input = {'key0a': "test", 'key0b': 
                 {'key1a': {'key2a': 'end', 'key2b': "test"} ,'key1b': "test"}, 
                 "something": "else"}
def parse(d):
  x = SimpleNamespace()
  _ = [setattr(x, k, parse(v)) if isinstance(v, dict) else setattr(x, k, v) for k, v in d.items() ]    
  return x

result = parse(example_input)
print (result)

Output: Output:

namespace(key0a='test', 
          key0b=namespace(key1a=namespace(key2a='end', key2b='test'), key1b='test'), 
          something='else')

2022 answer: now there is a tiny, relatively fast library I have published, called dotwiz , which alternatively can be used to provide easy dot access for a python dict object. 2022 回答:现在我发布了一个很小、相对较快的库,称为dotwiz ,它也可用于为 python dict object 提供简单的点访问

It should, coincidentally, be a little faster than the other options -- I've added a quick and dirty benchmark code I put together using the timeit module below, timing against both a attrdict and SimpleNamespace approach -- the latter of which actually performs pretty solid in times.巧合的是,它应该比其他选项快一点——我添加了一个快速而肮脏的基准代码,我使用下面的timeit模块放在一起,针对attrdictSimpleNamespace方法计时——后者实际上执行有时相当稳固。

Note that I had to modify the parse function slightly, so that it handles nested dict s within a list object, for example.请注意,例如,我必须稍微修改parse function,以便它处理list object 中的嵌套dict

from timeit import timeit
from types import SimpleNamespace

from attrdict import AttrDict
from dotwiz import DotWiz


example_input = {'key0a': "test", 'key0b': {'key1a': [{'key2a': 'end', 'key2b': "test"}], 'key1b': "test"},
                 "something": "else"}


def parse(d):
    x = SimpleNamespace()
    _ = [setattr(x, k,
                 parse(v) if isinstance(v, dict)
                 else [parse(e) for e in v] if isinstance(v, list)
                 else v) for k, v in d.items()]
    return x


print('-- Create')
print('attrdict:         ', round(timeit('AttrDict(example_input)', globals=globals()), 2))
print('dotwiz:           ', round(timeit('DotWiz(example_input)', globals=globals()), 2))
print('SimpleNamespace:  ', round(timeit('parse(example_input)', globals=globals()), 2))
print()

dw = DotWiz(example_input)
ns = parse(example_input)
ad = AttrDict(example_input)

print('-- Get')
print('attrdict:         ', round(timeit('ad.key0b.key1a[0].key2a', globals=globals()), 2))
print('dotwiz:           ', round(timeit('dw.key0b.key1a[0].key2a', globals=globals()), 2))
print('SimpleNamespace:  ', round(timeit('ns.key0b.key1a[0].key2a', globals=globals()), 2))
print()

print(ad)
print(dw)
print(ns)

assert ad.key0b.key1a[0].key2a \
       == dw.key0b.key1a[0].key2a \
       == ns.key0b.key1a[0].key2a \
       == 'end'

Here are the results, on my M1 Mac Pro laptop:以下是我的 M1 Mac Pro 笔记本电脑上的结果:

attrdict:          0.69
dotwiz:            1.3
SimpleNamespace:   1.38

-- Get
attrdict:          6.06
dotwiz:            0.06
SimpleNamespace:   0.06

The dotwiz library can be installed with pip : dotwiz库可以与pip一起安装:

$ pip install dotwiz

Based on mujjija's solution this is what I came up with.基于 mujjija 的解决方案,这就是我想出的。 Full code below完整代码如下

from types import SimpleNamespace


def parse(data):
    if type(data) is list:
        return list(map(parse, data))
    elif type(data) is dict:
        sns = SimpleNamespace()
        for key, value in data.items():
            setattr(sns, key, parse(value))
        return sns
    else:
        return data


info = {
    'country': 'Australia',
    'number': 1,
    'slangs': [
        'no worries mate',
        'winner winner chicken dinner',
        {
            'no_slangs': [123, {'definately_not': 'hello'}]
        }
    ],
    'tradie': {
        'name': 'Rizza',
        'occupation': 'sparkie'
    }
}

d = parse(info)
assert d.country == 'Australia'
assert d.number == 1
assert d.slangs[0] == 'no worries mate'
assert d.slangs[1] == 'winner winner chicken dinner'
assert d.slangs[2].no_slangs[0] == 123
assert d.slangs[2].no_slangs[1].definately_not == 'hello'
assert d.tradie.name == 'Rizza'
assert d.tradie.occupation == 'sparkie'

If I'm not mistaken, Python doesn't support Tail Call Optimization.如果我没记错的话,Python 不支持尾调用优化。 So please be careful when using deep recursive functions in Python.所以在Python中使用深度递归函数时请注意。 For small examples, it should be fine.对于小例子,应该没问题。

Update更新

Another version.另一个版本。 object_hook does the magic of nesting. object_hook具有嵌套的魔力。 I prefer this version because I can directly feed them to the jinja2 template engine.我更喜欢这个版本,因为我可以直接将它们提供给jinja2模板引擎。

import json


class _DotDict(dict):
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__


def dot(data=None):
    if data is []:
        return []
    return json.loads(json.dumps(data), object_hook=_DotDict) if data else _DotDict()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM