在Python中复杂地将嵌套字典转换为对象

7 投票
4 回答
4621 浏览
提问于 2025-04-16 13:36

我刚开始学习Python没多久,但我真的很想深入研究这个语言,努力去掌握它。现在我遇到一个任务,研究了一段时间但还没搞定:
我有一个混合了嵌套字典和列表的组合(我们称它为组合),我需要实现一个函数,让我们可以像访问对象属性一样访问这些嵌套的元素,同时还要能把组合中的元素当作可迭代的对象。这看起来应该像这样:

combination = {
'item1': 3.14,
'item2': 42,
'items': [
         'text text text',
         {
             'field1': 'a',
             'field2': 'b',
         },
         {
             'field1': 'c',
             'field2': 'd',
         },
         ]
}

def function(combination):
    ...

这样
list(function(combination).items.field1) 会返回:['a', 'c'],而
list(function(combination).item1) 会返回:[3.14]
编辑 正如@FM提到的,我漏掉了处理非字典元素的描述: list(function(combination).items[0]) >>> ['text text text']


我尝试实现一个类(感谢Marc)来帮助我:

class Struct:
    def __init__(self, **entries): 
        self.__dict__.update(entries)

然后在函数中使用它,比如return Struct(**combination)
虽然这很巧妙,但这只是达到目标的第一步。
接下来的步骤需要更深入的理解,这让我感到有些不知所措,我自己搞不定。
所以,我诚恳地请求大家的帮助。

迈克尔。

4 个回答

1

我觉得你这里大概有两个选择:

  1. function把嵌套的数据结构转换成一系列相互连接的对象,这些对象需要实现支持list()dict()的协议(这些对象必须实现一些函数,至少包括__iter____len____getitem__等)。为了创建这些对象,你需要定义一些类来实现这些功能,然后递归地组合它们,或者使用type()动态创建类。

  2. function返回一个类,这个类可以代理对底层数据结构的访问。为了实现一个允许访问非实际成员属性的类(也就是说,像这样使用function(combination).items),你需要重写__getattr__。你不能在这个函数的单次调用中访问到“完整的点路径”,所以它必须递归操作,并在每个点路径的层级返回额外的实例。我认为这个方法会比第一个简单。

3

例如:

class Struct:
    def __init__(self, **entries):
        for key, value in entries.items():
            value2 = (Struct(**value) if isinstance(value, dict) else value)
            self.__dict__[key] = value2

entries = {
    "a": 1,
    "b": {
        "c": {
            "d": 2
        }
    }
}

obj = Struct(**entries)
print(obj.a) #1
print(obj.b.c.d) #2
6

这样怎么样:

class ComboParser(object):
    def __init__(self,data):
        self.data=data
    def __getattr__(self,key):
        try:
            return ComboParser(self.data[key])
        except TypeError:
            result=[]
            for item in self.data:
                if key in item:
                    try:
                        result.append(item[key])
                    except TypeError: pass
            return ComboParser(result)
    def __getitem__(self,key):
        return ComboParser(self.data[key])
    def __iter__(self):
        if isinstance(self.data,basestring):
            # self.data might be a str or unicode object
            yield self.data
        else:
            # self.data might be a list or tuple
            try:
                for item in self.data:
                    yield item
            except TypeError:
                # self.data might be an int or float
                yield self.data
    def __length_hint__(self):
        return len(self.data)

这样会得到:

combination = {
    'item1': 3.14,
    'item2': 42,
    'items': [
        'text text text',
        {
            'field1': 'a',
            'field2': 'b',
            },
        {
            'field1': 'c',
            'field2': 'd',
            },
        {
            'field1': 'e',
            'field3': 'f',
            },        
        ]
    }
print(list(ComboParser(combination).item1))
# [3.1400000000000001]
print(list(ComboParser(combination).items))
# ['text text text', {'field2': 'b', 'field1': 'a'}, {'field2': 'd', 'field1': 'c'}, {'field3': 'f', 'field1': 'e'}]
print(list(ComboParser(combination).items[0]))
# ['text text text']
print(list(ComboParser(combination).items.field1))
# ['a', 'c', 'e']

撰写回答