繁体   English   中英

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

[英]How to convert a nested python 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"))

如果给出了 example_input 字典,是否有更好的替代方法可以使字典的键可以按点表示法访问(例如 example_input.key0a) - 没有外部依赖项?

只是回答你的第二个(最后一个)问题 - 这是一个热门话题,存在许多不同的项目(非标准),可以让你的字典变成点符号 object。

例如这个 one- attrdict 通过pip install attrdict安装它。

使用示例:

在线尝试!

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

如果你想知道attrdict 之类的模块是如何实现的,那么我写了一个非常简单的类似功能的实现(当然真正的attrdict应该更丰富):

在线尝试!

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 {}

使用递归

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:

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

2022 回答:现在我发布了一个很小、相对较快的库,称为dotwiz ,它也可用于为 python dict object 提供简单的点访问

巧合的是,它应该比其他选项快一点——我添加了一个快速而肮脏的基准代码,我使用下面的timeit模块放在一起,针对attrdictSimpleNamespace方法计时——后者实际上执行有时相当稳固。

请注意,例如,我必须稍微修改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'

以下是我的 M1 Mac Pro 笔记本电脑上的结果:

attrdict:          0.69
dotwiz:            1.3
SimpleNamespace:   1.38

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

dotwiz库可以与pip一起安装:

$ pip install dotwiz

基于 mujjija 的解决方案,这就是我想出的。 完整代码如下

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'

如果我没记错的话,Python 不支持尾调用优化。 所以在Python中使用深度递归函数时请注意。 对于小例子,应该没问题。

更新

另一个版本。 object_hook具有嵌套的魔力。 我更喜欢这个版本,因为我可以直接将它们提供给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