今回は,Python用のライブラリとして,PyYAML,libyaml for Pythonの2つのライブラリを紹介します。
PyYAML
PyYAMLは,Pythonで実装されたYAMLライブラリです。特徴としては,YAMLの最新仕様に厳密に準拠していることです。そのため,現在ではYAMLにおけるリファレンス実装だとみなされています。
PyYAMLについて
| Webサイト | http://pyyaml.org/wiki/PyYAML |
|---|---|
| ドキュメント | http://pyyaml.org/wiki/ PyYAMLDocumentation |
| ダウンロード | http://pyyaml.org/download/pyyaml/ |
| バージョン | 3.05 |
| 作者 | Kirill Simonov |
インストール
インストールは,LinuxやMac OS Xではリスト1の手順で行ってください。Windowsの場合は,http://pyyaml.org/download/pyyaml/にあるWindows installerを使ってください。
リスト1 PyYAMLのインストール
$ wget http://pyyaml.org/download/pyyaml/PyYAML-3.05.tar.gz $ tar xzf PyYAML-3.05.tar.gz $ cd PyYAML-3.05/ $ sudo python setup.py install
使い方
PyYAMLの使い方はリスト2のようになります。日本語を含む場合は必ずUnicodeにデコードしなければならない点に注意してください。詳細はリファレンスマニュアルを参照してください。
リスト2 PyYAMLの使い方(ex-pyyaml1.py)
import yaml
## YAMLドキュメントを読み込む
string = open('example.yaml').read()
string = string.decode('utf8') # 日本語を含む場合はデコードする
data = yaml.load(string)
print repr(data)
## または
## data = yaml.load(open('example.yaml'))
## print repr(data)
## YAMLストリームを読み込む
for data in yaml.load_all(string):
print repr(data)
## または
## for data in yaml.load_all(open('example.yaml')):
## print repr(data)
## 任意のデータをYAML文字列に変換する
## (allow_unicode=Trueを指定すると、日本語がエンコードされない)
data = [ {'x':10, 'y':20}, {'x':15, 'y':25} ]
print yaml.dump(data, encoding='utf8', allow_unicode=True)
## またはファイルに出力する場合:
## f = open('dump.yaml', 'w')
## yaml.dump(data, f, encoding='utf8', allow_unicode=True)
またYAMLでは任意のオブジェクトを変換/復元できます。しかしそのために,たとえば信用できない相手から受け取ったYAML文字列をそのままyaml.load()でロードすると,セキュリティ的に問題のあるデータに変換される可能性があります。これを防ぐために,PyYAMLでは次の関数が用意されています。
- yaml.safe_load()
- yaml.load()と同じですが,「!!python/object:__main__.Hello」のようなタグがあるとエラーになります。
- yaml.safe_dump()
- yaml.dump()と同じですが,listやmapやスカラー以外のオブジェクトがあるとエラーになります。
これらを使うと,安全なデータのみを相手と受け渡しできます。
タグを変更する
yaml.dump()でインスタンスオブジェクトをYAML文字列に変換した場合,たとえば「!!python/object:__main__.Color」のようなタグがつきます。しかしこれだとPython限定になり,ほかの言語のYAMLパーサで読み込むとエラーになるため,データ交換する場合には都合が悪いです。
これを回避するには,以下の関数を使ってクラスに対応するタグを指定します(リスト3)。
- yaml.add_representer(klass, func)
- オブジェクトをノード(yaml.nodes.Node)に変換する関数を登録します。
- yaml.add_constructor(ytag, func)
- ノード(yaml.nodes.Node)からオブジェクトを復元する関数を登録します。
リスト3 クラスに対応するタグを指定する(ex-pyyaml2.py)
# -*- coding: utf-8 -*-
import yaml
## クラスに対応するタグを登録するユーティリティ
def yaml_register_class(klass, ytag):
suffix = '%s.%s' % (klass.__module__, klass.__name__)
def representer(dumper, instance):
node = dumper.represent_mapping(ytag, instance.__dict__)
return node
def constructor(loader, node):
instance = loader.construct_python_object(suffix, node)
return instance
yaml.add_representer(klass, representer)
yaml.add_constructor(ytag, constructor)
## または
#suffix = '%s.%s' % (klass.__module__, klass.__name__)
#f1 = lambda dumper, obj: dumper.represent_mapping(ytag, obj.__dict__)
#f2 = lambda loader, node: loader.construct_python_object(suffix, node)
#yaml.add_representer(klass, f1)
#yaml.add_constructor(ytag, f2)
## クラス定義
class Color(object):
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
## クラスに対応するタグを登録する
yaml_register_class(Color, '!color')
## 実行例
c = Color(255, 128, 0)
print yaml.dump(c) #=> !color {b: 0, g: 128, r: 255}
s = '!color {b: 0, g: 128, r: 255}'
c = yaml.load(s)
print c #=> <__main__.Color object at 0x5be510>
print c.__dict__ #=> {'r': 255, 'b': 0, 'g': 128}
.--------------------
またオブジェクトをマッピング以外のデータで表現することもできます。リスト4は,文字列またはシーケンスでデータを表現した例です。
リスト4 オブジェクトを文字列またはシーケンスで表現する(ex-pyyaml3.py)
# -*- coding: utf-8 -*-
import yaml
## クラス定義
class Color(object):
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
## Colorインスタンスをノードに変換する関数
def color_representer(dumper, color):
## 文字列に変換する場合
s = '#%02x%02x%02x' % (color.r, color.g, color.b)
node = dumper.represent_scalar(u'!color', s)
return node
## シーケンスに変換する場合
#L = [color.r, color.g, color.b]
#node = dumper.represent_mapping(u'!color', L)
#return node
yaml.add_representer(Color, color_representer)
## 実行例
print yaml.dump(Color(255, 0, 0))
#=> !color '#ff0000' (スカラーに変換した場合)
#=> !color [255, 0, 0] (シーケンスに変換した場合)
## ノードからColorインスタンスを復元する関数
def color_constructor(loader, node):
## 文字列を表すノードから変換する場合
s = loader.construct_scalar(node)
import re
pat = '#' + '([a-fA-F0-9][a-fA-F0-9])' * 3
m = re.match(pat, s)
if not m:
msg = '%s: invalid color' % s
raise yaml.constructor.ConstructorError(msg)
r, g, b = m.groups()
return Color(int(r, 16), int(g, 16), int(b, 16))
## シーケンスを表すノードから変換する場合
#L = loader.construct_sequence(node)
#return Color(L[0], L[1], L[2])
yaml.add_constructor(u'!color', color_constructor)
## 実行例(文字列から変換した場合)
color = yaml.load("!color '#FF8000'")
print "color: r=%s, g=%s, b=%s" % (color.r, color.g, color.b)
#=> color: r=255, g=128, b=0
## 実行例(シーケンスから変換した場合)
#color = yaml.load('!color [255, 128, 0]')
#print "color: r=%s, g=%s, b=%s" % (color.r, color.g, color.b)
# #=> color: r=255, g=128, b=0
日本語の扱い
yaml.load()では,入力となる文字列をデコードしてunicodeに変換しておけば,特に問題はありません。
yaml.dump()では,デフォルトでは日本語文字列がたとえば「\u3044」のようにエンコードされますが,allow_unicode=Trueを指定するとエンコードされません。またUnicodeを変換するためのencodingを指定できます。
その他
Pythonのlistオブジェクトやdictオブジェクトは,次のようなルールでYAMLのコンテナに変換されます(リスト5)。
- ほかのコンテナを含んでいればブロックスタイル
- ほかのコンテナを含んでいなければフロースタイル
フロースタイルを使わずすべてをブロックスタイルにしたい場合は,yaml.dump()においてdefault_flow_style=Falseを指定してください。
リスト5 yaml.dump()におけるフロースタイルとブロックスタイル(ex-pyyaml4.py)
## point1とpoint2はコンテナを含んでいないのでフロースタイル、
## pointsはコンテナを含んでいるのでブロックスタイルになる。
point1 = {'x':10, 'y':20}
point2 = {'x':15, 'y':25}
points = [ point1, point2 ]
print yaml.dump(points),
## 実行結果:
## - {x: 10, y: 20}
## - {x: 15, y: 25}
## default_flow_style=Falseを指定すると、すべてブロックスタイルになる。
print yaml.dump(points, default_flow_style=False),
## 実行結果:
## - x: 10
## y: 20
## - x: 15
## y: 25

