如何映射到字典而不是列表?

1 投票
2 回答
1530 浏览
提问于 2025-04-16 06:39

我有一个函数,它的基本功能是把一个lxml对象转换成字典...

from lxml import etree 

tree = etree.parse('file.xml')
root = tree.getroot()

def xml_to_dict(el):
    d={}
    if el.text:
        print '***write tag as string'
        d[el.tag] = el.text
    else:
        d[el.tag] = {}
    children = el.getchildren()
    if children:
        d[el.tag] = map(xml_to_dict, children)
    return d

    v = xml_to_dict(root)

现在它给我的结果是....

>>>print v
{'root': [{'a': '1'}, {'a': [{'b': '2'}, {'b': '2'}]}, {'aa': '1a'}]}

但是我想要的是....

>>>print v
{'root': {'a': ['1', {'b': [2, 2]}], 'aa': '1a'}}

我该如何重写这个函数xml_to_dict(el),才能得到我想要的输出呢?

这是我正在解析的xml,为了更清楚。

<root>
    <a>1</a>
    <a>
        <b>2</b>
        <b>2</b>
    </a>
    <aa>1a</aa>
</root>

谢谢 :)

2 个回答

2

更简单:

from lxml import etree    
def recursive_dict(element):
    return element.tag, dict(map(recursive_dict, element)) or element.text

使用方法:

   >> tree = etree.parse(file_name)
   >> recursive_dict(tree.getroot())
   ('root', {'tag1': text, 'tag2': subtag21: {tag211: text}})

编辑:问题示例的输出结果:

('root', {'a': {'b': '2'}, 'aa': '1a'})

看起来etree会跳过重复的元素。

5

好吧,map() 函数总是会返回一个列表,所以简单的说就是“别用 map()”。相反,你可以像现在这样,通过遍历 children 来构建一个字典,把 xml_to_dict(child) 的结果赋值给你想用的字典键。看起来你想用标签作为键,然后把值设置为一个包含该标签的项目列表,所以可以这样做:

import collections
from lxml import etree

tree = etree.parse('file.xml')
root = tree.getroot()

def xml_to_dict(el):
    d={}
    if el.text:
        print '***write tag as string'
        d[el.tag] = el.text
    child_dicts = collections.defaultdict(list)
    for child in el.getchildren():
        child_dicts[child.tag].append(xml_to_dict(child))
    if child_dicts:
        d[el.tag] = child_dicts
    return d

xml_to_dict(root)

这样的话,字典中的标签条目会变成一个默认字典;如果你出于某种原因想要一个普通的字典,可以用 d[el.tag] = dict(child_dicts)。需要注意的是,像之前提到的,如果一个标签同时有文本和子元素,文本不会出现在字典里。你可能需要考虑一下字典的不同布局来处理这个问题。

编辑:

生成你重新表述的问题中输出的代码不会在 xml_to_dict 中递归——因为你只想为外层元素生成一个字典,而不是为所有子标签生成字典。所以,你可以使用类似这样的代码:

import collections
from lxml import etree

tree = etree.parse('file.xml')
root = tree.getroot()

def xml_to_item(el):
    if el.text:
        print '***write tag as string'
        item = el.text
    child_dicts = collections.defaultdict(list)
    for child in el.getchildren():
        child_dicts[child.tag].append(xml_to_item(child))
    return dict(child_dicts) or item

def xml_to_dict(el):
    return {el.tag: xml_to_item(el)}

print xml_to_dict(root)

不过,这样仍然不能合理处理同时有文本和子元素的标签,并且将 collections.defaultdict(list) 转换成了普通字典,所以输出(几乎)符合你的预期:

***write tag as string
***write tag as string
***write tag as string
***write tag as string
***write tag as string
***write tag as string
{'root': {'a': ['1', {'b': ['2', '2']}], 'aa': ['1a']}}

(如果你真的想要 b 标签中的文本数据是整数而不是字符串,你需要以某种方式明确地将它们转换为整数。)

撰写回答