連載
» 2023年12月26日 05時00分 公開

[解決!Python]PyYAMLモジュールを使ってYAMLデータを読み込むには解決!Python

YAMLドキュメントをPythonオブジェクトに変換するためのPyYAMLモジュールの使い方と、YAMLドキュメントがどんな形でPythonオブジェクトに変換されるかを紹介する。

[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

from yaml import safe_load, unsafe_load, full_load
from pathlib import Path  # ファイル内容の確認用

# YAMLファイルからの読み出し
print(Path('test.yaml').read_text())
# 出力結果:
#---  # YAMLドキュメントの開始
#- foo: FOO
#- bar: BAR
#- baz: BAZ
#...  # YAMLドキュメントの終了

with open('test.yaml') as f:
    data = safe_load(f)

print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]

# 文字列からの読み出し
s = """
- foo: FOO  # これはYAMLのコメント
- bar: BAR
- baz: BAZ
"""

data = full_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]

import yaml
from yaml import load
data = load(s, Loader=yaml.UnsafeLoader)  # yaml.UnsafeLoaderとyaml.Loaderは同じ

print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]

# シーケンスのパース
s = """
- foo  # ブロックシーケンス
- bar
- baz
"""

data0 = safe_load(s)  # load(s, Loader=yaml.SafeLoader)
print(data0)  # ['foo', 'bar', 'baz']

s = """
[foo, bar, baz]  # フローシーケンス
"""
data1 = full_load(s)  # load(s, Loader=yaml.FullLoader)
print(data1)  # ['foo', 'bar', 'baz']

print(data0 == data1)  # True

# ネストしたブロックシーケンス
s = """
- L1-1
-  # 次レベル
- L2-1
- L2-2
- L1-2  # レベルを元に
- # 次レベル
  -  # 次レベル
    - L3-1
    - L3-2
"""
data = safe_load(s)
print(data)  # ['L1-1', ['L2-1', 'L2-2'], 'L1-2', [['L3-1', 'L3-2']]]

# ネストしたフローシーケンス
s = """
[L1-1, [L2-1, L2-2], L1-2, [[L3-1, L3-2]]]
"""
data = safe_load(s)
print(data)  # ['L1-1', ['L2-1', 'L2-2'], 'L1-2', [['L3-1', 'L3-2']]]

# マッピング
s = """
foo: FOO  # ブロックマッピング
bar: BAR
baz: BAZ
"""

data = safe_load(s)
print(data)  # {'foo': 'FOO', 'bar': 'BAR', 'baz': 'BAZ'}

s = """
{foo: FOO, bar: BAR, baz: BAZ}  # フローマッピング
"""

data = safe_load(s)
print(data)  # {'foo': 'FOO', 'bar': 'BAR', 'baz': 'BAZ'}

# ネストしたブロックマッピング(マッピングを値とするマッピング)
s = """
user01:
  name: isshiki
  id: 100
user02:
  name: kawasaki
  id: 102
"""

data = safe_load(s)
print(data)
# 出力結果:
#{'user01': {'name': 'isshiki', 'id': 100}, 'user02': {'name': 'kawasaki',
# 'id': 102}}

# シーケンスを値とするマッピング
s = """
user01:
  - isshiki  # ブロックシーケンス
  - 100
user02: [kawasaki, 102]  # フローシーケンス
"""

data = safe_load(s)
print(data)  # {'user01': ['isshiki', 100], 'user02': ['kawasaki', 102]}

# マッピングを要素とするシーケンス
s = """
- foo: FOO
- bar: BAZ
"""

data = safe_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAZ'}]

s = """
[
foo: FOO,
{bar: BAR}
]
"""

data = safe_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}]

# スカラー(Unicode文字列)
s = """
forums: |  # リテラルスタイル(改行はそのまま)
  windows insider
  deep insider
foo: >  # foldedスタイル(途中の改行は空白文字に変換される)
  foo and
  bar
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')
# 出力結果:
#forums: windows insider  # 改行はそのまま
#deep insider
#
#foo: foo and bar  # andの後の改行が半角空白文字に

s = """
single quoted: 'single
    quoted scalar'
double quoted: "double
    quoted scalar"
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')
# 出力結果:
#single quoted: single quoted scalar
#double quoted: double quoted scalar

# YAMLでの明示的な型指定
s = """
int: 1  # PyYAMLが型を自動的に変換してくれる
float: 2.0
datetime: 2023-12-26T05:00:00+0900
local datetime: 2023-12-26 05:00:00
date: 2023-12-05
time: 05:00:00
str: string
sequence: [0, 1, 2]
mapping: {foo: FOO, bar: BAR}
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v},\ttype: {type(v)}')
# 出力結果:
#int: 1, type: <class 'int'>
#float: 2.0,     type: <class 'float'>
#datetime: 2023-12-26T05:00:00+0900,     type: <class 'str'>
#local datetime: 2023-12-26 05:00:00,    type: <class 'datetime.datetime'>
#date: 2023-12-05,       type: <class 'datetime.date'>
#time: 05:00:00, type: <class 'str'>
#str: string,    type: <class 'str'>
#sequence: [0, 1, 2],    type: <class 'list'>
#mapping: {'foo': 'FOO', 'bar': 'BAR'},  type: <class 'dict'>

# YAMLタグ
s = """
none: !!null
int: !!int 20
float: !!float 10
bool: [!!bool true, !!bool false, !!bool yes, !!bool no]
list of tuple: !!omap [{foo: FOO}, {bar: BAR}]
list: !!seq [0, 1, 2]
dict: !!map {foo: FOO, bar: BAR}
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')

# Pythonに固有のタグ
s = """
none: !!python/none
bool: [!!python/bool true, !!python/bool off]
bytes: !!python/bytes ZGVlcA==
str: !!python/str python
int: !!python/int 1
float: !!python/float 10
list: !!python/list [foo, bar, baz]
tuple: !!python/tuple [foo, bar, baz]
dict: !!python/dict {foo: FOO, bar: BAR}
"""

data = full_load(s)
for k, v in data.items():
    print(f'{k}: {v}')

data = safe_load(s)  # yaml.constructor.ConstructorError
data = unsafe_load(s)  # OK

# 複数のYAMLドキュメントをまとめて読み出す
s = """
---
foo: FOO
...
---
bar: BAR
...
"""

from yaml import safe_load_all

contents = safe_load_all(s)
for idx, content in enumerate(contents, 1):
    print(f'{idx}番目のドキュメント')
    print(content)
# 出力結果:
#1番目のドキュメント
#{'foo': 'FOO'}
#2番目のドキュメント
#{'bar': 'BAR'}

print(Path('test2.yaml').read_text())
# 出力結果:
# 1つのストリーム(ファイル)に複数のYAMLドキュメント
#---
#foo: FOO  # 1つ目のYAMLドキュメント
#...
#---
#bar: BAR  # 2つ目のYAMLドキュメント
#...

with open('test2.yaml') as f:
    contents = safe_load_all(f)

for content in contents:
    print(content)  # ValueError: I/O operation on closed file.

with open('test2.yaml') as f:
    contents = safe_load_all(f)
    result = [content for content in contents]

for content in result:
    print(content)
# 出力結果:
#{'foo': 'FOO'}
#{'bar': 'BAR'}


関数 使われるローダー 説明
load Loaderパラメーターに指定が必須 ローダーの指定による
safe_load SafeLoader YAML仕様のサブセットを読み込み可能。安全にパースできる。信頼できないソースからのYAMLデータの読み込みにはこれを使用する
full_load FullLoader YAML仕様のフルセットに対応し、安全にパースができるとともに任意のコードの実行は不可能となっているが、信頼できないソースからのYAMLデータの読み込みには使わないこと
unsafe_load UnsafeLoader(Loader) YAML仕様のフルセットに対応し、任意のコードの実行も可能。使用してもよいと思われるのは、信頼できるソースからのYAMLドキュメントを読み込む場合のみ
safe_load_all SafeLoader 1つのストリーム(文字列や辞書など)に複数のYAMLドキュメントが含まれている場合に、それらを反復するジェネレーターを返送する。同様な関数としてfull_load_all関数、load_all関数、unsafe_load_all関数もある
YAMLデータの読み込みに使用できる関数

PyYAMLモジュール

 YAML(YAML Ain't Markup Language)はオブジェクトやデータをシリアライズするための言語仕様であり、可読性が高いことが特徴となっている。YAML形式で記述されたドキュメント(YAMLドキュメント)をPythonで扱うためのモジュールには幾つかあるか、ここではPyYAMLを使用する。なお、YAMLの最新仕様は1.2.2だが、2023年12月22日の時点ではPyYAMLはYAML 1.1を対象としている点には注意されたい。YAML 1.2をサポートしているruamel.yamlもあるので必要な型はそちらもチェックしよう。

 以下ではPyYAMLを用いて、YAMLドキュメントをファイルまたは文字列からPythonのオブジェクトとして読み出す方法を紹介する。PyYAMLはPythonには標準で添付されていないので「pip install pyyaml」「py -m pip install pyyaml」などの方法で事前にインストールしておく必要がある。

PyYAMLでYAMLドキュメントを読み出す基本

 PyYAMLには、YAMLドキュメントをPythonのオブジェクト(リストや辞書)に変換するための関数が数多く含まれている(モジュール名は「yaml」となる)。

  • load関数:読み込みに使用するローダーをLoaderパラメーターで指定する必要がある
  • safe_load関数:YAML言語仕様のサブセットをサポート。YAMLドキュメント読み出し時に任意のコードの実行が不可能になっているので、安全にYAMLドキュメントを読み出せる
  • full_load関数:YAML言語仕様をフルにサポート。ただし、任意のコードの実行は不可能。だが、信頼できないソースからのYAMLドキュメントの読み出しに使用しないこと
  • unsafe_load関数:YAMLドキュメントの読み出し時に任意のコードの実行が可能。そのため、この関数を使ってYAMLドキュメントを読み出すのであれば、信頼できるソースからのYAMLドキュメントのみを対象とすること

 実際にはsafe_load/full_load/unsafe_load関数は、load関数のLoaderパラメーターにそれぞれSafeLoader/FullLoader/UnsafeLoaderを指定した呼び出すものだと考えればよい。また、1つのYAMLファイルには複数のYAMLドキュメントを格納できるが、上記の関数群は先頭のYAMLドキュメントしか読み出せない。全てのYAMLドキュメントを読み出すには末尾に「_all」を付けたload_all/safe_load_all/full_load_all/unsafe_load_all関数を使用する。

 以下は、YAMLファイルからYAMLドキュメントを読み出す例だ。

from yaml import safe_load, unsafe_load, full_load
from pathlib import Path  # ファイル内容の確認用

# YAMLファイルからの読み出し
print(Path('test.yaml').read_text())
# 出力結果:
#---  # YAMLドキュメントの開始
#- foo: FOO
#- bar: BAR
#- baz: BAZ
#...  # YAMLドキュメントの終了

with open('test.yaml') as f:
    data = safe_load(f)

print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]


 上のtest.yamlファイルには3つのマッピングを要素とするシーケンスが記述されている(「-」で始まる3行がシーケンスを表し、その内容である「foo: FOO」などがマッピングを表している)。また、「---」はYAMLドキュメントの開始を、「...」はYAMLドキュメントの終了を意味し、「#」以降はコメントとなっている。

 このYAMLファイルをwith文でオープンし、その内容をここではsafe_load関数で読み出している。このときに、マッピングは辞書に、シーケンスはリストに変換される。そのため、safe_load関数で読み出した内容は辞書を要素とするリストになっている。

 safe_load関数(その他の関数)はファイルだけではなく、YAML形式で記述された内容の文字列を読み出して、Pythonのオブジェクトに変換することも可能だ。以下に例を示す。

s = """
- foo: FOO  # これはYAMLのコメント
- bar: BAR
- baz: BAZ
"""

data = full_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]


 ここでは文字列の内容は先ほどのtest.yamlファイルと同様となっている(YAMLドキュメントを1つだけ含むときには「---」「...」は省略可能)。それを今度はfull_load関数で読み出している。結果は先ほどと同様だ。

 上述した通り、safe_load関数はSafeLoaderを、full_load関数はFullLoaderを、unsafe_load関数はUnsafeLoaderを、load関数のLoaderパラメーターに指定するのと同様であるため、以下のような書き方も可能である。

import yaml
from yaml import load
data = load(s, Loader=yaml.UnsafeLoader)  # yaml.UnsafeLoaderとyaml.Loaderは同じ

print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}, {'baz': 'BAZ'}]


 以下では、文字列にYAMLドキュメントを記述して、それをsafe_load関数で読み出す形でYAMLドキュメントを読み出すと、それがPythonのオブジェクトにどのように変換されるかを簡単に見ていく。

シーケンス

 YAMLのシーケンスとは、0個以上の要素(YAMLではノードと呼ぶ)が順序付きで並んだものと考えられる。シーケンスにはブロックシーケンスとフローシーケンスの2種類の記述方法がある。ブロックシーケンスは「-」で行を始め、そこに1つの要素を記述する。フローシーケンスは「[]」の中に要素をカンマ区切りで並べていく。

 PyYAMLではシーケンスはリストに変換される。以下はブロックシーケンスと、それをsafe_load関数でPythonのオブジェクトに読み出す例だ。

s = """
- foo  # ブロックシーケンス
- bar
- baz
"""

data0 = safe_load(s)  # load(s, Loader=yaml.SafeLoader)
print(data0)  # ['foo', 'bar', 'baz']


 以下はフローシーケンスをfull_load関数で読み出して、Pythonのオブジェクトに変換する例だ。

s = """
[foo, bar, baz]  # フローシーケンス
"""
data1 = full_load(s)  # load(s, Loader=yaml.FullLoader)
print(data1)  # ['foo', 'bar', 'baz']


 下の行の実行結果からは、記法が違っても、上の2つの例で作成した2つのオブジェクト(data0とdata1)が同等であることが分かる。

print(data0 == data1)  # True


 ブロックシーケンスとフローシーケンスはどちらもネストさせ、シーケンスの要素としてシーケンスを持たせられる。以下に例を示す。

# ネストしたブロックシーケンス
s = """
- L1-1
-  # 次レベル
- L2-1
- L2-2
- L1-2  # レベルを元に
- # 次レベル
  -  # 次レベル
    - L3-1
    - L3-2
"""
data = safe_load(s)
print(data)  # ['L1-1', ['L2-1', 'L2-2'], 'L1-2', [['L3-1', 'L3-2']]]

# ネストしたフローシーケンス
s = """
[L1-1, [L2-1, L2-2], L1-2, [[L3-1, L3-2]]]
"""
data = safe_load(s)
print(data)  # ['L1-1', ['L2-1', 'L2-2'], 'L1-2', [['L3-1', 'L3-2']]]


 ブロックシーケンスにおいては、「-」とインデントで各要素のネストの仕方が区別される。最初は「-」とインデントによってシーケンスがどのようにネストするかが分かりづらいかもしれないが、これはYAMLファイルを手で書いていくうちに慣れていくだろう。

 上の結果を見ると分かるが、ネストしたシーケンスはリストのリストに変換される。

マッピング

 YAML仕様ではマッピングとは順序を持たない、キー/値の組みのことで、PyYAMLでは辞書に変換される。マッピングもブロックマッピングとフローマッピングの2種類の記述方法がある。以下にブロックマッピングをsafe_load関数で読み出す例を示す。

s = """
foo: FOO  # ブロックマッピング
bar: BAR
baz: BAZ
"""

data = safe_load(s)
print(data)  # {'foo': 'FOO', 'bar': 'BAR', 'baz': 'BAZ'}


 ブロックマッピングでは1行に「キー: 値」を1つずつ記述していく。一方、フローマッピングでは、「{}」の中に「キー: 値」をカンマ区切りで並べていく。以下に例を示す。

s = """
{foo: FOO, bar: BAR, baz: BAZ}  # フローマッピング
"""

data = safe_load(s)
print(data)  # {'foo': 'FOO', 'bar': 'BAR', 'baz': 'BAZ'}


 シーケンスと同様、マッピングもネストが可能だ。

# ネストしたブロックマッピング(マッピングを値とするマッピング)
s = """
user01:
  name: isshiki
  id: 100
user02:
  name: kawasaki
  id: 102
"""

data = safe_load(s)
print(data)
# 出力結果:
#{'user01': {'name': 'isshiki', 'id': 100}, 'user02': {'name': 'kawasaki',
# 'id': 102}}


シーケンスとマッピングの組み合わせ

 以下は、シーケンスとマッピングを組み合わせて記述する例だ。

s = """
user01:
  - isshiki  # ブロックシーケンス
  - 100
user02: [kawasaki, 102]  # フローシーケンス
"""

data = safe_load(s)
print(data)  # {'user01': ['isshiki', 100], 'user02': ['kawasaki', 102]}


 この例では、シーケンスをマッピングの値として使用している。これはリストを値とする辞書に変換される。

 次にマッピングを要素とするシーケンスの例を示す。

s = """
- foo: FOO
- bar: BAZ
"""

data = safe_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAZ'}]

s = """
[
foo: FOO,
{bar: BAR}
]
"""

data = safe_load(s)
print(data)  # [{'foo': 'FOO'}, {'bar': 'BAR'}]


 2つ目の例では、フローシーケンスを改行込みで記述して、その中にマッピングをカンマで並べている点に注目しよう。

スカラー(Unicode文字列)

 YAMLにおけるスカラーとは0文字以上のUnicode文字の並びのことだ。スカラーにもブロック形式とフロー形式の記述方法がある。ブロック形式のスカラーでは改行の扱いを指示する「|」と「>」が重要になる。「|」を指定した場合、リテラルスタイルと呼ばれ、改行はそのまま改行として扱われる。「>」を指定した場合、foldedスタイルと呼ばれ、途中の改行は空白文字に変換される。

 以下に例を示す。

s = """
forums: |  # リテラルスタイル(改行はそのまま)
  windows insider
  deep insider
foo: >  # foldedスタイル(途中の改行は空白文字に変換される)
  foo and
  bar
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')
# 出力結果:
#forums: windows insider  # 改行はそのまま
#deep insider
#
#foo: foo and bar  # andの後の改行が半角空白文字に


 フロー形式のスカラーではシングルクオートで囲む、ダブルクオートで囲む、どちらも使わない、の3種類の記述方法がある。以下に例を示す。

s = """
single quoted: 'single
    quoted scalar'
double quoted: "double
    quoted scalar"
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')
# 出力結果:
#single quoted: single quoted scalar
#double quoted: double quoted scalar


YAMLでの明示的な型指定

 PyYAMLはYAMLドキュメントに記述された値を自動的にPythonの対応するオブジェクトに変換してくれる。以下に例を示す。

s = """
int: 1  # PyYAMLが型を自動的に変換してくれる
float: 2.0
datetime: 2023-12-26T05:00:00+0900
local datetime: 2023-12-26 05:00:00
date: 2023-12-05
time: 05:00:00
str: string
sequence: [0, 1, 2]
mapping: {foo: FOO, bar: BAR}
"""

data = safe_load(s)


 この例では、整数や浮動小数点数、日付、文字列、シーケンス、マッピングが値として記述されている。これらがどんなオブジェクトに変換されるかを見てみよう。

for k, v in data.items():
    print(f'{k}: {v},\ttype: {type(v)}')
# 出力結果:
#int: 1, type: <class 'int'>
#float: 2.0,     type: <class 'float'>
#datetime: 2023-12-26T05:00:00+0900,     type: <class 'str'>
#local datetime: 2023-12-26 05:00:00,    type: <class 'datetime.datetime'>
#date: 2023-12-05,       type: <class 'datetime.date'>
#time: 05:00:00, type: <class 'str'>
#str: string,    type: <class 'str'>
#sequence: [0, 1, 2],    type: <class 'list'>
#mapping: {'foo': 'FOO', 'bar': 'BAR'},  type: <class 'dict'>


 時刻が文字列になっていることを除けば、多くは問題なく、変換されていることが分かる。が、変換後の型をYAMLドキュメントの側で指定することも可能だ。これにはYAMLのタグと呼ばれる機能を使用する。

 型指定に使えるタグにどんなものがあるかについてはPyYAMLのドキュメント「YAML tags and Python types」を参照されたい。YAMLで標準的にサポートされているタグは「!!」に続けて「null」「int」などのデータ型が続き、その後に実際の値を記述する。以下に例を示す。

s = """
none: !!null
int: !!int 20
float: !!float 10
bool: [!!bool true, !!bool false, !!bool yes, !!bool no]
list of tuple: !!omap [{foo: FOO}, {bar: BAR}]
list: !!seq [0, 1, 2]
dict: !!map {foo: FOO, bar: BAR}
"""

data = safe_load(s)
for k, v in data.items():
    print(f'{k}: {v}')
# 出力結果:
#none: None
#int: 20
#float: 10.0
#bool: [True, False, True, False]
#list of tuple: [('foo', 'FOO'), ('bar', 'BAR')]
#list: [0, 1, 2]
#dict: {'foo': 'FOO', 'bar': 'BAR'}


 文字列sには「!!null」「!!int」などのタグを用いて変換後の型を明示的に与えている。変換後の出力結果を見ると、指定通りに変換できていることが分かる(yes/noがTrue/Falseに変換されている点に注目)。

 一方で「YAML tags and Python types」にはPython固有のタグも紹介されている。それらのタグは「!!」に続けて「python/データ型」の形で変換後のデータ型を指定する。以下に例を示す。

s = """
none: !!python/none
bool: [!!python/bool true, !!python/bool off]
bytes: !!python/bytes ZGVlcA==
str: !!python/str python
int: !!python/int 1
float: !!python/float 10
list: !!python/list [foo, bar, baz]
tuple: !!python/tuple [foo, bar, baz]
dict: !!python/dict {foo: FOO, bar: BAR}
"""

data = full_load(s)
for k, v in data.items():
    print(f'{k}: {v}')

data = safe_load(s)  # yaml.constructor.ConstructorError
data = unsafe_load(s)  # OK


 標準的なYAMLのタグに対して、bytes型(!!python/bytes)やtuple型などの型指定が可能になっている点に注目されたい。なお、最後の2行にあるように、safe_load関数ではPythonに固有のタグは変換ができない点には注意しよう(full_load関数とunsafe_load関数では変換可能)。

複数のYAMLドキュメントをまとめて読み出す

 最後に複数のYAMLドキュメントを読み出す方法を紹介しておこう。既に述べたが、1つのストリーム(ここでは1つのYAMLファイルや1つの文字列だと考えられる)には複数のYAMLドキュメントを格納できる。YAMLドキュメントは「---」で始まり、「...」で終了する。しかし、今までに見てきたsafe_loadなどの関数は複数のYAMLドキュメントを含んでいるストリームには対応していない。その代わりにsafe_load_allなどの関数が用意されている。

 例えば、次のように1つの文字列に複数のYAMLドキュメントが書かれていたとする。

s = """
---
foo: FOO
...
---
bar: BAR
...
"""


 ここから2つのYAMLドキュメントの内容を読み出すには、例えばsafe_load_all関数が使える。以下に例を示す。

contents = safe_load_all(s)
for idx, content in enumerate(contents, 1):
    print(f'{idx}番目のドキュメント')
    print(content)
# 出力結果:
#1番目のドキュメント
#{'foo': 'FOO'}
#2番目のドキュメント
#{'bar': 'BAR'}


 safe_load_all関数はそれぞれのYAMLドキュメントを指すオブジェクトを反復するジェネレーターを返すので、上の例ではfor文でその内容を取り出している。

 ただし、YAMLファイルを扱うときには少し注意が必要だ。例えば、以下のようなtest2.yamlファイルがあったとする。

print(Path('test2.yaml').read_text())
# 出力結果:
# 1つのストリーム(ファイル)に複数のYAMLドキュメント
#---
#foo: FOO  # 1つ目のYAMLドキュメント
#...
#---
#bar: BAR  # 2つ目のYAMLドキュメント
#...


 このYAMLファイルの内容を取り出そうとして、以下のようなコードを書くと例外が発生する。

with open('test2.yaml') as f:
    contents = safe_load_all(f)

for content in contents:
    print(content)  # ValueError: I/O operation on closed file.


 これはジェネレーターを介して、YAMLドキュメントの内容をアクセスする前にファイルをクローズしてしまっているからだ。そのため、実際には次のようなコードを書くことになるだろう。

with open('test2.yaml') as f:
    contents = safe_load_all(f)
    result = [content for content in contents]

for content in result:
    print(content)
# 出力結果:
#{'foo': 'FOO'}
#{'bar': 'BAR'}


 ここではファイルをクローズする前に、YAMLドキュメントの内容を取得するようなコードとした。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。