python: есть ли парсер XML, реализованный как генератор? - PullRequest
8 голосов
/ 03 октября 2009

Я бы хотел проанализировать большой XML-файл "на лету". Я хотел бы использовать генератор Python для выполнения этого. Я пробовал "iterparse" из "xml.etree.cElementTree" (что действительно хорошо), но все еще не генератор.

Другие предложения?

Ответы [ 5 ]

15 голосов
/ 03 октября 2009

xml.etree.cElementTree подходит к генератору с правильным использованием; по умолчанию вы получаете каждый элемент после его события 'end', после чего вы можете обработать его. Вы должны использовать element.clear () для элемента, если он вам не нужен после обработки; тем самым вы экономите память.


Вот полный пример того, что я имею в виду, где я анализирую библиотеку Rhythmbox (Music Player). Я использую (c) элемент управления ElementTree и для каждого обработанного элемента я вызываю element.clear (), чтобы сэкономить довольно много памяти. (Кстати, приведенный ниже код является преемником некоторого саксофонного кода, который делает то же самое; решение cElementTree было облегчением, поскольку 1) код является кратким и выражает то, что мне нужно, и ничего более 2) он в 3 раза быстрее, 3) он использует меньше памяти.)

import os
import xml.etree.cElementTree as ElementTree
NEEDED_KEYS= set(("title", "artist", "album", "track-number", "location", ))

def _lookup_string(string, strmap):
    """Look up @string in the string map,
    and return the copy in the map.

    If not found, update the map with the string.
    """
    string = string or ""
    try:
        return strmap[string]
    except KeyError:
        strmap[string] = string
        return string

def get_rhythmbox_songs(dbfile, typ="song", keys=NEEDED_KEYS):
    """Return a list of info dictionaries for all songs
    in a Rhythmbox library database file, with dictionary
    keys as given in @keys.
    """
    rhythmbox_dbfile = os.path.expanduser(dbfile)

    lSongs = []
    strmap = {}

    # Parse with iterparse; we get the elements when
    # they are finished, and can remove them directly after use.

    for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
        if not (entry.tag == ("entry") and entry.get("type") == typ):
            continue
        info = {}
        for child in entry.getchildren():
            if child.tag in keys:
                tag = _lookup_string(child.tag, strmap)
                text = _lookup_string(child.text, strmap)
                info[tag] = text
        lSongs.append(info)
        entry.clear()
    return lSongs

Теперь, я не понимаю ваши ожидания, у вас есть следующие ожидания?

# take one
for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
    # parse some entries, then exit loop

# take two
for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
    # parse the rest of entries

Каждый раз, когда вы вызываете iterparse, вы получаете новый объект итератора, читающий файл заново! Если вам нужен постоянный объект с семантикой итератора, вы должны ссылаться на один и тот же объект в обоих циклах (непроверенный код):

#setup
parseiter = iter(ElementTree.iterparse(rhythmbox_dbfile))
# take one
for event, entry in parseiter:
    # parse some entries, then exit loop

# take two
for event, entry in parseiter:
    # parse the rest of entries

Я думаю, это может сбивать с толку, поскольку разные объекты имеют разную семантику. Файловый объект всегда будет иметь внутреннее состояние и продвигаться в файле, однако вы итерируете его. Элемент iterparse ElementTree, по-видимому, нет. Суть в том, чтобы думать, что когда вы используете цикл for, for всегда вызывает iter () для того, что вы повторяете. Вот эксперимент, сравнивающий ElementTree.iterparse с файловым объектом:

>>> import xml.etree.cElementTree as ElementTree
>>> pth = "/home/ulrik/.local/share/rhythmbox/rhythmdb.xml"
>>> iterparse = ElementTree.iterparse(pth)
>>> iterparse
<iterparse object at 0x483a0890>
>>> iter(iterparse)
<generator object at 0x483a2f08>
>>> iter(iterparse)
<generator object at 0x483a6468>
>>> f = open(pth, "r")
>>> f
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>
>>> iter(f)
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>
>>> iter(f)
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>

То, что вы видите, это то, что каждый вызов iter () для объекта iterparse возвращает новый генератор. Файловый объект, однако, имеет внутреннее состояние операционной системы, которое должно быть сохранено, и имеет свой собственный итератор.

6 голосов
/ 03 октября 2009

Деревья синтаксического анализа и документов "на лету" не совместимы. Для этого обычно используются парсеры в стиле SAX (например, стандарт Python xml.sax ). По сути, вам нужно определить класс с обработчиками для различных событий, таких как startElement, endElement и т. Д., И синтаксический анализатор будет вызывать методы при анализе файла XML.

4 голосов
/ 03 октября 2009

PullDom делает то, что вы хотите. Он читает XML из потока, как SAX, но затем создает DOM для выбранного его фрагмента.

"PullDOM - это действительно простой API для работы с объектами DOM потоковым (эффективным!) Способом, а не в виде монолитного дерева."

0 голосов
/ 27 февраля 2019

У xmltodict есть способ обратного чтения строки за строкой, но он не очень питоничен. Я хотел что-то подобное для чтения постов stackoverflow по одному из их дампа xml с помощью генератора.

Это структура XML-файла:

<?xml version="1.0" encoding="utf-8"?>
<posts>
  <row Id="1"  ... />
  <row Id="2" ... />
</posts>

А вот код, который я использовал. Он объединяет pulldom для потоковой передачи и xmltodict для анализа строк.

def xml_to_dict_gen(file_path, tag='row'):
    from xml.dom import pulldom
    import xmltodict
    doc = pulldom.parse(file_path)
    for event, node in doc:
        if event == pulldom.START_ELEMENT and node.tagName == tag:
            doc.expandNode(node)
            yield dict(xmltodict.parse(node.toxml()).get(tag))

for post in xml_to_dict_gen('Posts.xml'):
    print(post)
0 голосов
/ 01 января 2012

Это возможно с elementtree и инкрементальным разбором: http://effbot.org/zone/element-iterparse.htm#incremental-parsing

import xml.etree.cElementTree as etree
for event, elem in etree.iterparse(source):
    ...

Проще в использовании, чем саксофон.

...