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

第3回Python編

今回は、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

LibYAML for Python

LibYAMLは、Cで書かれた高速なライブラリです。LibYAML自体では動作せず、各プログラミング言語用のバインディングが必要です。今のところ、Python用のバインディングが用意されています。

LibYAML for Pythonについて
Webサイトhttp://pyyaml.org/wiki/PyYAML
ドキュメントhttp://pyyaml.org/wiki/LibYAML
ダウンロードhttp://pyyaml.org/download/libyaml
バージョン0.0.1
作者Kirill Simonov

インストール

LibYAMLのインストールは、リスト6の手順で行ってください。

リスト6 LibYAMLのインストール
$ wget http://pyyaml.org/download/libyaml/yaml-0.0.1.tar.gz
$ tar xzf yaml-0.0.1.tar.gz
$ cd yaml-0.0.1/
$ ./configure
$ make
$ sudo make install

またLibYAMLをPythonから使うには、Pythonバインディングが必要です。

インストールには、ほかにPyrexとPyYAMLが必要ですリスト7⁠。

リスト7 Pythonバインディングのインストール
$ wget http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/Pyrex-0.9.6.4.tar.gz
$ tar xzf Pyrex-0.9.6.4.tar.gz
$ cd Pyrex-0.9.6.4/
$ sudo python setup.py install
$ cd ..
$ 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_with_libyaml.py install

使い方

LibYAMLをPythonから使う方法は、以下の点を除いてPyYAMLと同じです。

  • yaml.load()において、引数に「Loader=CLoader」を指定する
  • yaml.dump()において、引数に「Dumper=CDumper」を指定する

つまりPyYAML標準のLoaderクラスとDumperクラスの代わりに、LibYAML'を使ったCLoaderクラスとCDumperクラスを指定するわけです。

リスト8は使い方のサンプルです。ここではLibYAML'がインストールされていてもいなくても動作するようにしています。

リスト8 LibYAMLの使い方(ex-libyaml1.py)
# -*- coding: utf-8 -*-
import yaml
try:
    ## LibYAMLがインストールされている場合はそれを使う
    from yaml import CLoader as Loader
    from yaml import CDumper as Dumper
except ImportError:
    ## インストールされていない場合はPyYAMLを使う
    from yaml import Loader, Dumper

## YAMLドキュメントを読み込む
import sys
string = open('example.yaml').read()
string = string.decode('utf8')      # 日本語を含む場合はデコードする
data = yaml.load(string, Loader=Loader)
## または
##   data = yaml.load(open('example.yaml'), Loader=Loader)

## 任意のデータをYAML文字列に変換する
data = [ {'x':10, 'y':20}, {'x':15, 'y':25} ]
print yaml.dump(data, Dumper=Dumper)
## またはファイルに出力する場合:
##   f = open('dump.yaml', 'w')
##   print yaml.dump(data, f, Dumper=Dumper)

日本語の扱い

日本語の扱いについては、PyYAMLと同じです。

タグを変更する

タグを変更する方法は、PyYAMLと同じです。

不具合

筆者が試した範囲では、特に不具合は見つかりませんでした。

その他

当然ですが、LibYAMLはC言語から使うことができます。詳細はlibyamlのWebサイトを参照してください。

おすすめ記事

記事・ニュース一覧