如何重写此函数以实现有序字典(OrderedDict)?

15
我有下列函数,它可以把一个XML文件粗略地解析成一个字典。不幸的是,由于Python字典是无序的,我不能按照想要的顺序循环遍历节点。
如何更改这个函数以输出一个有序的字典,可以在使用for循环遍历时反映出原始节点的顺序?
def simplexml_load_file(file):
    import collections
    from lxml import etree

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

    def xml_to_item(el):
        item = None
        if el.text:
            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)}

    return xml_to_dict(root)

x = simplexml_load_file('routines/test.xml')

print x

for y in x['root']:
    print y

输出:

{'root': {
    'a': ['1'],
    'aa': [{'b': [{'c': ['2']}, '2']}],
    'aaaa': [{'bb': ['4']}],
    'aaa': ['3'],
    'aaaaa': ['5']
}}

a
aa
aaaa
aaa
aaaaa

如何实现collections.OrderedDict,以便我可以确保获取节点的正确顺序?

参考 XML 文件:

<root>
    <a>1</a>
    <aa>
        <b>
            <c>2</c>
        </b>
        <b>2</b>
    </aa>
    <aaa>3</aaa>
    <aaaa>
        <bb>4</bb>
    </aaaa>
    <aaaaa>5</aaaaa>
</root>
3个回答

37
你可以使用新的OrderedDictdict子类,该子类在标准库的collections模块中添加到2.7版本中。实际上,你需要的是一个Ordered+defaultdict组合,这种组合并不存在,但是可以通过如下所示的子类化OrderedDict来创建一个:

如果你的Python版本没有OrderedDict,你应该能够使用Raymond Hettinger的Ordered Dictionary for Py2.4 ActiveState配方作为基类。

import collections

class OrderedDefaultdict(collections.OrderedDict):
    """ A defaultdict with OrderedDict as its base class. """

    def __init__(self, default_factory=None, *args, **kwargs):
        if not (default_factory is None or callable(default_factory)):
            raise TypeError('first argument must be callable or None')
        super(OrderedDefaultdict, self).__init__(*args, **kwargs)
        self.default_factory = default_factory  # called by __missing__()

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key,)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):  # Optional, for pickle support.
        args = (self.default_factory,) if self.default_factory else tuple()
        return self.__class__, args, None, None, iter(self.items())

    def __repr__(self):  # Optional.
        return '%s(%r, %r)' % (self.__class__.__name__, self.default_factory, self.items())

def simplexml_load_file(file):
    from lxml import etree

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

    def xml_to_item(el):
        item = el.text or None
        child_dicts = OrderedDefaultdict(list)
        for child in el.getchildren():
            child_dicts[child.tag].append(xml_to_item(child))
        return collections.OrderedDict(child_dicts) or item

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

    return xml_to_dict(root)

x = simplexml_load_file('routines/test.xml')
print(x)

for y in x['root']:
    print(y)

你的测试XML文件产生的输出如下:

{'root':
    OrderedDict(
        [('a', ['1']),
         ('aa', [OrderedDict([('b', [OrderedDict([('c', ['2'])]), '2'])])]),
         ('aaa', ['3']),
         ('aaaa', [OrderedDict([('bb', ['4'])])]),
         ('aaaaa', ['5'])
        ]
    )
}

a
aa
aaa
aaaa
aaaaa

我认为这已经接近你想要的了。

小更新:

添加了一个__reduce__()方法,允许该类的实例被正确地序列化和反序列化。这对于这个问题来说不是必需的,但在类似的问题中出现过。


这太棒了!有几个修复:a)在3.x中,您需要将“.iteritems()”调用更改为“.items()”(或者您可以使用six库进行2.x / 3.x兼容性)b)在3.7中几乎不需要此功能,因为语言现在保证字典保证插入顺序,除非您想要任意或变化的顺序。 - smci
@smci:我修改了代码,可以在Python 2.7和3.2+中运行。 - martineau
你不需要使用 Python 3.6+,你可以使用 defaultdict,它默认维护键的顺序。 - Mysterion

2

马丁诺的配方对我有用,但它在继承自DefaultDict的copy()方法上存在问题。以下方法修复了这个缺点:

class OrderedDefaultDict(OrderedDict):
    #Implementation as suggested by martineau

    def copy(self):
         return type(self)(self.default_factory, self)

请注意,这个实现没有进行深拷贝,在大多数情况下,这对于默认字典来说似乎不是正确的做法。

1

这里列举了很多有序字典(OrderedDict)的可能实现方式: 如何按照插入顺序从字典中检索项目?

如果你由于所使用的Python版本的原因无法访问OrderedDict,你可以通过复制其中一种实现方式来创建自己的有序字典模块,并在自己的代码中使用。

你问题中有一个有趣的方面是可能需要defaultdict的功能。如果你需要这个功能,你可以实现__missing__方法以达到你想要的效果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接