Python でプロパティリストを読み込む

2008/08/05 12:21pm

Python でプロパティリストを読み込み、オブジェクトに変換するライブラリ plist_parser を書いた。GitHub公開している。Python 2.4 以降で動作確認済み。ただし、対応するフォーマットは XML のみで、書き込みにも対応していない。

使い方は至って単純だ。たとえば、以下のプロパティリストは iTunes Music Library.xml から一部抜粋したものだが、

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Track ID</key>
  <integer>4154</integer>
  <key>Artist</key>
  <string>Megadeth</string>
  <key>Name</key>
  <string>Architecture Of Aggression</string>
  <key>Date Added</key>
  <date>2008-06-23T12:20:17Z</date>
</dict>
</plist>

このプロパティリストが music.xml という名前で保存されているとして、このファイルを読み込んでオブジェクトに変換するコードは以下のとおり。

from plist_parser import XmlPropertyListParser, \
                         PropertyListParseError

f = open('music.plist')
try:
    print XmlPropertyListParser().parse(f)
    # =>
    # {
    #    'Date Added': datetime.datetime(2008, 6, 23, 12, 20, 17),
    #    'Track ID': 4154,
    #    'Name': 'Architecture Of Aggression',
    #    'Artist': 'Megadeth'
    # }
except PropertyListParseError:
    raise
finally:
    f.close()

特に説明の必要はないだろう。

自作ライブラリの紹介はこれくらいにし、以降では、Python でプロパティリストを解析する他の手段と、それらのパフォーマンスを比較した結果について書いてみたい。

plistlib

Python 2.6 からは、プロパティリストの読み書きをするためのライブラリ plistlib が標準で添付されている。また、Python 2.5 以前でもソースコードや Mac 向けのディストリビューションには含まれているので、実はプロパティリストを読み込みたいだけなら、今回自作した plist_parser の有用性はすくない。

それなら、何故わざわざ自作したのかといえば、ほぼ実装してしまってから plistlib の存在に気づいたのだ。勘弁してほしい。

xml.etree

Python 2.5 から標準添付されている xml.etree ライブラリを使うと XML の処理が簡潔に記述できる。作者のサイト effbot.org に掲載されている記事 The ElementTree iterparse Function に、ちょうど、このライブラリを使ってプロパティリストを読み込む例がある(以下はすこし改変してある)。

from xml.etree.ElementTree import iterparse
import base64, datetime, re

unmarshallers = {

    # collections
    "array": lambda x: [v.text for v in x],
    "dict": lambda x:
        dict((x[i].text, x[i+1].text) for i in range(0, len(x), 2)),
    "key": lambda x: x.text or "",

    # simple types
    "string": lambda x: x.text or "",
    "data": lambda x: base64.decodestring(x.text or ""),
    "date": lambda x: datetime.datetime(*map(int, re.findall("\d+", x.text))),
    "true": lambda x: True,
    "false": lambda x: False,
    "real": lambda x: float(x.text),
    "integer": lambda x: int(x.text),

}

def load(file):
    parser = iterparse(file)
    for action, elem in parser:
        unmarshal = unmarshallers.get(elem.tag)
        if unmarshal:
            data = unmarshal(elem)
            elem.clear()
            elem.text = data
        elif elem.tag != "plist":
            raise IOError("unknown plist type: %r" % elem.tag)
    return parser.root[0].text

非常にコンパクトだ。

xml.etree は C による実装 xml.etree.cElementTree も提供されており、これを使うとパフォーマンスでもかなりの好成績を出すようになる(具体的なパフォーマンスの比較は後述)。

パフォーマンスの比較

さて、プロパティリストを読み込むためのライブラリとして、

の三つが出揃った。これらのパフォーマンスを比較してみよう。

それぞれのライブラリを使用したプログラムを用意する。測定対象のプログラムはどれも、iTunes の音楽ライブラリのプロパティリスト(約 7MB)を読み込み、オブジェクトに変換する、というものである。これらのプログラムをそれぞれ 20 回ずつ実行し、その実行時間の平均を比較する

また、プログラムと測定方法については、以下の点にも気をつけた。

しかし、実行時間はプログラムを実行するコンピューターの性能に左右される。誰かのコンピューターで 5 秒しかかからなかった、と言われても、あなたにとっては意味がないだろう。それぞれのプログラムの実行時間を並べていっても、実際にどれくらい速いのか(あるいは、遅いのか)はわかりづらい。基準となる比較対象が必要だ。

今回は比較対象として、C による実装を用意した。これは Mac OS X ネイティブの API を使って、プロパティリストを読み込んでいる。このプログラムの実行時間を 1 として、それぞれの実行時間を比較してみよう(なお、測定に使用したプログラムはここに置いてある)。

Performance of Python Property Lists Librariesやはり、C によるプログラムが桁違いに速い。ちなみに、実時間でいえば 0.5 秒程度である。

次に目立つのが xml.etree によるプログラムだ。これは C による xml.etree.cElementTree と Python のみで実装された xml.etree.ElementTree のふたつがあるが、xml.etree.cElementTree はかなり速いことがわかる。C によるプログラムと比較しても 5 倍程度の実行時間、plistlib と比較すると 3 倍程度速く、優秀であるといえる。逆に、Python による実装の xml.etree.ElementTree がズバ抜けて遅いのも興味深い。

ところで、グラフからも分かるように、plist_parser には実装がふたつある。

実は、今回の測定で xml.etree.cElementTree が速いことが判り、xml.etree.cElementTree がインストールされているときはそちらを使うように急遽作りかえたのだ(ない場合は、xml.sax による実装を使う)。

そのため、Python 2.5 以降など、xml.etree.cElementTree がインストールされている環境では、同程度の速度で動作するようになっている。

最後に