在Python中复杂地将嵌套字典转换为对象
我刚开始学习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 个回答
我觉得你这里大概有两个选择:
让
function
把嵌套的数据结构转换成一系列相互连接的对象,这些对象需要实现支持list()
和dict()
的协议(这些对象必须实现一些函数,至少包括__iter__
、__len__
、__getitem__
等)。为了创建这些对象,你需要定义一些类来实现这些功能,然后递归地组合它们,或者使用type()
动态创建类。让
function
返回一个类,这个类可以代理对底层数据结构的访问。为了实现一个允许访问非实际成员属性的类(也就是说,像这样使用function(combination).items
),你需要重写__getattr__
。你不能在这个函数的单次调用中访问到“完整的点路径”,所以它必须递归操作,并在每个点路径的层级返回额外的实例。我认为这个方法会比第一个简单。
例如:
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
这样怎么样:
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']