由于后续的.clear(),无法迭代孩子的孩子?

时间:2012-09-11 12:35:01

标签: python xml-parsing lxml iterparse

我正在尝试使用lxml教程的"event-driven parsing"部分中描述的模式。

在我的代码中,我正在调用一个可以使用iterchildren()方法递归运行元素的函数。我将在这里使用两个嵌套循环进行说明。

这可以按预期工作:

xml = StringIO("<root><a><b>data</b><c><d/></c></a><a><z/></a></root>")
for ev, elem in etree.iterparse(xml):
    if elem.tag == 'a':
        for c in elem.iterchildren():
             for gc in c.iterchildren():
                  print gc

输出为<Element d at 0x2df49b0>

但如果我最后添加.clear()

for ev, elem in etree.iterparse(xml):
    if elem.tag == 'a':
        for c in elem.iterchildren():
             for gc in c.iterchildren():
                  print gc
    elem.clear()

- 它不会打印任何东西。为什么会如此?如何解决这个问题?

注意:

  • 我可以跳过iterchildrenfor c in elemfor c in list(elem),效果相同。
  • 我需要使用迭代方法来降低内存使用率。
  • 在实际使用案例中,我正在使用属性进行元素查找:

    if elem.attrib.get('id') == elem_id:
        return _get_info(elem)
    

我想解释clear如何在处理内部元素之前将其擦除,以及如何在处理祖先时将它们保存在内存中。

1 个答案:

答案 0 :(得分:2)

问题是默认iterparse会产生end个事件。对于子元素end,事件的生成时间早于其祖先:

>>> for ev, elem in etree.iterparse(xml):
       print elem

<Element b at 0x38fe320>
<Element d at 0x38fe0f0>
<Element c at 0x38fe2d0>
<Element a at 0x38fe190>
<Element z at 0x38fe230>
<Element a at 0x38fe3c0>
<Element root at 0x2df48c0>

在这个简单的情况下,可以通过依赖start事件来解决问题:

for ev, elem in etree.iterparse(xml, events=('start',)):
    ...

然而,链接的文档说,

  

请注意,Element的text,tail和children不是   在接收开始事件时必然存在。只有结束   event保证Element已被完全解析。

这可能意味着应该处理startend个事件,以便只有在安全的情况下才能调用clear()。我正在考虑实施某种堆栈并在start个事件上推送,弹出和clear end个事件。

我非常欢迎使用这个或其他想法展示实现的答案。

注意:最简单的方法就是将clear调用缩进if。这将允许访问孙子,但所有不相关的元素将保持不clear


修改

我目前正在以下列方式使用状态保持变量:

found = False
for event, elem in etree.iterparse(source, events=('start', 'end')):
    if event == 'start':
        if elem.attrib.get('id') == elem_id:
            found = True
    else:
        if elem.attrib.get('id') == elem_id:
            return _get_info(elem)
        if not found:
            elem.clear()