【问题标题】:Why is lxml.etree.iterparse() eating up all my memory?为什么 lxml.etree.iterparse() 会占用我所有的内存?
【发布时间】:2012-08-28 13:34:03
【问题描述】:

这最终会耗尽我所有的可用内存,然后进程被终止。我尝试将标签从 schedule 更改为“更小”标签,但这并没有什么不同。

我做错了什么/如何使用iterparse() 处理这个大文件?

import lxml.etree

for schedule in lxml.etree.iterparse('really-big-file.xml', tag='schedule'):
    print "why does this consume all my memory?"

我可以轻松地将其切割成更小的块进行处理,但这比我想要的更难看。

【问题讨论】:

    标签: python xml memory lxml iterparse


    【解决方案1】:

    iterparse 遍历整个文件时,会构建一棵树,并且不会释放任何元素。这样做的好处是元素会记住它们的父元素,并且您可以形成引用祖先元素的 XPath。缺点是会消耗大量内存。

    为了在解析时释放一些内存,请使用 Liza Daly 的 fast_iter

    def fast_iter(context, func, *args, **kwargs):
        """
        http://lxml.de/parsing.html#modifying-the-tree
        Based on Liza Daly's fast_iter
        http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
        See also http://effbot.org/zone/element-iterparse.htm
        """
        for event, elem in context:
            func(elem, *args, **kwargs)
            # It's safe to call clear() here because no descendants will be
            # accessed
            elem.clear()
            # Also eliminate now-empty references from the root node to elem
            for ancestor in elem.xpath('ancestor-or-self::*'):
                while ancestor.getprevious() is not None:
                    del ancestor.getparent()[0]
        del context
    

    你可以像这样使用它:

    def process_element(elem):
        print "why does this consume all my memory?"
    
    context = lxml.etree.iterparse('really-big-file.xml', tag='schedule', events=('end',))
    fast_iter(context, process_element)
    

    我强烈推荐上述fast_iter 所基于的the article;如果您正在处理大型 XML 文件,这对您来说应该特别有趣。

    上面显示的fast_iter 是文章中显示的稍作修改的版本。这个更积极地删除以前的祖先,从而节省更多内存。 Here you'll find a script 演示了 区别。

    【讨论】:

    • 谢谢!您的解决方案和我刚刚添加的解决方案似乎都可以解决问题,我很好奇您和其他人认为哪个解决方案更好。你有什么想法吗?
    • 原来你的解决方案有效,而 effbot.org/zone/element-iterparse.htm 解决方案没有(它仍然吞噬了我所有的记忆)
    • 谢谢!这是真正有效的版本。来自 Liza Daly、effbot 和 lxml 官方文档的版本并没有为我节省太多内存。
    【解决方案2】:

    直接复制自http://effbot.org/zone/element-iterparse.htm

    请注意,iterparse 仍然会构建树,就像 parse 一样,但您可以在解析时安全地重新排列或删除树的某些部分。例如,要解析大文件,您可以在处理完元素后立即删除它们:

    for event, elem in iterparse(source):
        if elem.tag == "record":
            ... process record elements ...
            elem.clear()
    

    上述模式有一个缺点;它不会清除根元素,因此您最终会得到一个包含许多空子元素的元素。如果您的文件很大,而不仅仅是很大,这可能是个问题。要解决此问题,您需要掌握根元素。最简单的方法是启用开始事件,并保存对变量中第一个元素的引用:

    # get an iterable
    context = iterparse(source, events=("start", "end"))
    
    # turn it into an iterator
    context = iter(context)
    
    # get the root element
    event, root = context.next()
    
    for event, elem in context:
        if event == "end" and elem.tag == "record":
            ... process record elements ...
            root.clear()
    

    【讨论】:

    • 请注意,context.next() 在 Python 3 中变为 next(context)
    【解决方案3】:

    这对我来说非常有效:

    def destroy_tree(tree):
        root = tree.getroot()
    
        node_tracker = {root: [0, None]}
    
        for node in root.iterdescendants():
            parent = node.getparent()
            node_tracker[node] = [node_tracker[parent][0] + 1, parent]
    
        node_tracker = sorted([(depth, parent, child) for child, (depth, parent)
                               in node_tracker.items()], key=lambda x: x[0], reverse=True)
    
        for _, parent, child in node_tracker:
            if parent is None:
                break
            parent.remove(child)
    
        del tree
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-13
      • 1970-01-01
      • 2012-02-12
      • 2021-07-13
      • 1970-01-01
      • 2010-11-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多