8

大きなXMLファイルを「オンザフライ」で解析したいと思います。これを実行するためにPythonジェネレーターを使用したいと思います。「xml.etree.cElementTree」の「iterparse」を試しましたが(これは本当に素晴らしいです)、それでもジェネレーターではありません。

他の提案?

4

5 に答える 5

15

xml.etree.cElementTree正しい使用法で発電機に近づきます。デフォルトでは、'end'イベントの後に各要素を受け取り、その時点で処理できます。処理後に要素が必要ない場合は、要素に対してelement.clear()を使用する必要があります。それにより、メモリを節約できます。


これが私が意味する完全な例です。ここでは、Rhythmboxの(音楽プレーヤー)ライブラリを解析します。(c)ElementTreeのiterparseを使用し、処理された要素ごとにelement.clear()を呼び出して、かなりのメモリを節約します。(ところで、以下のコードは同じことを行うためのいくつかのsaxコードの後継です; 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

オブジェクトが異なればセマンティクスも異なるため、混乱を招く可能性があると思います。ファイルオブジェクトは常に内部状態を持ち、ファイル内で進みますが、それを繰り返します。ElementTreeiterparseオブジェクトは明らかにそうではありません。重要なのは、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>

ご覧のとおり、iterparseオブジェクトでiter()を呼び出すたびに、新しいジェネレーターが返されます。ただし、ファイルオブジェクトには、保存する必要のある内部オペレーティングシステムの状態と、それ自体のイテレータがあります。

于 2009-10-03T12:40:25.940 に答える
6

「オンザフライ」の解析ツリーとドキュメントツリーは、実際には互換性がありません。通常、SAXスタイルのパーサーが使用されます(たとえば、Pythonの標準xml.sax)。基本的に、startElement、endElementなどのさまざまなイベントのハンドラーを使用してクラスを定義する必要があります。パーサーはXMLファイルを解析するときにメソッドを呼び出します。

于 2009-10-03T12:20:44.043 に答える
5

PullDomはあなたが望むことをします。SAXのようにストリームからXMLを読み取りますが、選択した部分のDOMを構築します。

「PullDOMは、モノリシックツリーとしてではなく、ストリーミング(効率的!)な方法でDOMオブジェクトを操作するための非常にシンプルなAPIです。」

于 2009-10-03T12:30:57.567 に答える
0

これは、elementtreeおよびインクリメンタル解析で可能です:http: //effbot.org/zone/element-iterparse.htm#incremental-parsing

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

サックスより使いやすいです。

于 2012-01-01T14:10:47.280 に答える
0

xmltodictには、行ごとに読み取るコールバック方法がありますが、あまりPython的ではありません。ジェネレーターを使用してxmlダンプからstackoverflowの投稿を1つずつ読み取るための同様の何かが必要でした。

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)
于 2019-02-27T16:29:25.733 に答える