¿Por qué lxml.etree.iterparse () está consumiendo toda mi memoria?

Esto eventualmente consume toda mi memoria disponible y luego se elimina el proceso. He intentado cambiar la etiqueta de schedule a tags “más pequeñas”, pero eso no hizo una diferencia.

¿Qué estoy haciendo mal / cómo puedo procesar este archivo grande con iterparse() ?

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

Puedo cortarlo fácilmente y procesarlo en trozos más pequeños, pero eso es más feo de lo que me gustaría.

Como iterparse todo el archivo, se construye un árbol y no se liberan elementos. La ventaja de hacer esto es que los elementos recuerdan quiénes son sus padres, y puede formar XPaths que se refieren a elementos de antepasados. La desventaja es que puede consumir mucha memoria.

Para liberar algo de memoria mientras analizas, usa fast_iter Liza Daly:

 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 

que luego podrías usar así:

 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) 

Recomiendo altamente el artículo en el que se basa el fast_iter anterior; debería ser especialmente interesante para usted si está tratando con grandes archivos XML.

El fast_iter presentado anteriormente es una versión ligeramente modificada de la que se muestra en el artículo. Este es más agresivo sobre la eliminación de los antepasados ​​anteriores, por lo que ahorra más memoria. Aquí encontrarás un script que demuestra la diferencia.

Copiado directamente desde http://effbot.org/zone/element-iterparse.htm

Tenga en cuenta que iterparse todavía crea un árbol, al igual que el análisis, pero puede reorganizar o eliminar partes del árbol de manera segura mientras analiza. Por ejemplo, para analizar archivos grandes, puede deshacerse de los elementos tan pronto como los haya procesado:

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

El patrón anterior tiene un inconveniente; no borra el elemento raíz, por lo que terminará con un solo elemento con muchos elementos secundarios vacíos. Si sus archivos son enormes, en lugar de ser grandes, esto podría ser un problema. Para solucionar este problema, debe tener en sus manos el elemento raíz. La forma más fácil de hacer esto es habilitar eventos de inicio y guardar una referencia al primer elemento en una variable:

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

Esto funcionó muy bien para mí:

 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