Python でプロパティリストを読み込む
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 回ずつ実行し、その実行時間の平均を比較する。
また、プログラムと測定方法については、以下の点にも気をつけた。
- IO の影響をすくなくするため、ファイルはあらかじめメモリに展開する
- メモリ管理の影響をすくなくするため、プログラムの実行中は GC を停止する(C プログラムの場合は、メモリを解放しない)
- 負荷の高いバックグラウンドプロセス(たとえば Time Machine など)はオフにする
- ウォームアップのために、事前にプログラムを 5 回実行する
しかし、実行時間はプログラムを実行するコンピューターの性能に左右される。誰かのコンピューターで 5 秒しかかからなかった、と言われても、あなたにとっては意味がないだろう。それぞれのプログラムの実行時間を並べていっても、実際にどれくらい速いのか(あるいは、遅いのか)はわかりづらい。基準となる比較対象が必要だ。
今回は比較対象として、C による実装を用意した。これは Mac OS X ネイティブの API を使って、プロパティリストを読み込んでいる。このプログラムの実行時間を 1 として、それぞれの実行時間を比較してみよう(なお、測定に使用したプログラムはここに置いてある)。
やはり、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
がインストールされている環境では、同程度の速度で動作するようになっている。
最後に
- XML を扱うなら、xml.etree は一見の価値あり
- ライブラリを作るまえに、同じものがすでにないかチェックした方がよい
- パフォーマンスを測定するのは難しい