Почему lxml.etree.iterparse() съедает всю мою память?

В конечном итоге он потребляет всю доступную память, а затем процесс уничтожается. Я попытался изменить тег с 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?"

Я могу легко отрезать его и обработать его небольшими кусками, но это уродливое, чем хотелось бы.

Ответы

Ответ 1

Поскольку iterparse выполняет итерацию по всему файлу, дерево строится и никакие элементы не освобождаются. Преимущество этого заключается в том, что элементы помнят, кто их родитель, и вы можете сформировать XPaths, которые относятся к элементам предка. Недостатком является то, что он может потреблять много памяти.

Чтобы освободить некоторую память при анализе, используйте Liza Daly fast_iter:

def fast_iter(context, func, *args, **kwargs):
    """
    http://lxml.de/parsing.html#modifying-the-tree
    Based on Liza Daly 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 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; это должно быть особенно интересно для вас, если вы имеете дело с большими XML файлами.

Представленный выше fast_iter представляет собой слегка измененную версию показанного в статье. Это более агрессивно относится к удалению предыдущих предков, таким образом экономит больше памяти. Здесь вы найдете script, который демонстрирует разница.

Ответ 2

Непосредственно скопировано из http://effbot.org/zone/element-iterparse.htm

Обратите внимание, что iterparse по-прежнему строит дерево, как и синтаксический анализ, но вы можете безопасно изменить или удалить части дерева при разборе. Например, чтобы анализировать большие файлы, вы можете избавиться от элементов, как только вы их обработали:

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()