言語別 YAML用ライブラリ徹底解説

第3回 Python編

この記事を読むのに必要な時間:およそ 5 分

今回は,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
注6
http://pyyaml.org/wiki/PyYAML

著者プロフィール

桑田誠(くわたまこと)

プログラマー。Javaに対するLL,XMLに対するYAMLなど,複雑なことをシンプルにするような技術に興味をもつ。

URLhttp://www.kuwata-lab.com/

コメント

コメントの記入