[解決!Python]tomllibモジュールを使ってTOMLファイルを読み込むには解決!Python

Python 3.11で追加されたtomllibモジュールでTOMLファイルを読み込む方法と、TOMLに関する基本をまとめた。

» 2023年12月12日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

from tomllib import load, loads

with open('test.toml','rb') as f:
    d = load(f)

for k, v in d.items():
    print(f'{k}: {v}, type: {type(v)}')
# 出力結果:
#myname: deep insider, type: <class 'str'>
#myint: 1, type: <class 'int'>
#myfloat: 1.0, type: <class 'float'>
#myboolean: True, type: <class 'bool'>
#mydatetime: 2023-12-12 05:00:00+09:00, type: <class 'datetime.datetime'>
#mylocaldatetime: 2023-12-12 05:00:00, type: <class 'datetime.datetime'>
#mylocaldate: 2023-12-12, type: <class 'datetime.date'>
#mylocaltime: 05:00:00, type: <class 'datetime.time'>

data = """
key = 'value'
"""

d = loads(data)
print(d)  # {'key': 'value'}


# test.tomlファイル
myname = "deep insider"
myint = 1
myfloat = 1.0
myboolean = true  # 偽値はfalse
mydatetime = 2023-12-12T05:00:00.0000+09:00  # オフセット付き
mylocaldatetime = 2023-12-12 05:00:00.0000  # Tの代わりに空白文字も可
mylocaldate = 2023-12-12
mylocaltime = 05:00:00.0000



tomllibモジュールによるTOMLファイルの読み込み

 Python 3.11ではtomllibモジュールによるTOML(Tom's Obvious Minimal Language)サポートが追加された。ここでは、tomllibモジュールを使ったTOMLファイルの読み込みと、TOMLについて簡単に説明する。なお、tomllibモジュールではTOMLの書き出しについてはサポートされていない(これについては別稿で取り扱う予定だ)。

 TOMLは「ミニマルな構成ファイルフォーマットであることを目指して」おり、「明快なセマンティクスを持ち、読みやすい」ことが特徴となっている。tomllibモジュールでTOMLファイルまたはTOMLフォーマットで記述された文字列をパースすると、辞書が生成される。

 tomllibモジュールは以下の2つの関数を提供する。

  • load関数:TOMLファイルを読み込んでパースした結果を辞書として返す
  • loads関数:TOMLフォーマットで記述された文字列をパースし、その結果を辞書として返す

 例えば、次のようなTOMLファイルがあったとする(test.toml)。

myname = "deep insider"
myint = 1
myfloat = 1.0
myboolean = true  # 偽値はfalse
mydatetime = 2023-12-12T05:00:00.0000+09:00  # オフセット付き
mylocaldatetime = 2023-12-12 05:00:00.0000  # Tの代わりに空白文字も可
mylocaldate = 2023-12-12
mylocaltime = 05:00:00.0000



 これを見ると分かるが、TOMLでは基本的に「キー=値」の組みを記述していく。このファイルを読み込むにはload関数を使う。

from tomllib import load, loads

with open('test.toml','rb') as f:
    d = load(f)


 注意が必要なのはTOMLファイル自体はUTF-8でエンコードされたテキストファイルであってもtomllib.load関数で読み込む際にはバイナリとしてファイルをオープンする点だ。load関数が返すのは辞書なので、次のようにしてそのキー/値の組みを表示できる。

for k, v in d.items():
    print(f'{k}: {v}, type: {type(v)}')
# 出力結果:
#myname: deep insider, type: <class 'str'>
#myint: 1, type: <class 'int'>
#myfloat: 1.0, type: <class 'float'>
#myboolean: True, type: <class 'bool'>
#mydatetime: 2023-12-12 05:00:00+09:00, type: <class 'datetime.datetime'>
#mylocaldatetime: 2023-12-12 05:00:00, type: <class 'datetime.datetime'>
#mylocaldate: 2023-12-12, type: <class 'datetime.date'>
#mylocaltime: 05:00:00, type: <class 'datetime.time'>


 TOMLでは値としては以下が記述可能で、それらはPythonでは対応する型のオブジェクトに変換される。

パース後のPythonのオブジェクト型
文字列 str
整数 int
浮動小数点数 float(parse_floatパラメーターで変更可能)
ブール値 bool
オフセット付きの日時 datetime.datetime
ローカルの日時 datetime.datetime
ローカルの日付 datetime.date
ローカルの時間 datetime.time
配列 リスト
インラインテーブル 辞書
TOMLの値と対応するPythonの型

 上の例では配列、インラインテーブルの例を示していないが、これについては後述する。

 TOMLフォーマットで記述された文字列はtomllib.loads関数でパースすることで辞書に変換される。以下に例を示す。

data = """
key = 'value'
"""

d = loads(data)
print(d)  # {'key': 'value'}


 この例ではシンプルに「key = 'value'」という文字列をtomllib.loads関数でパースし、辞書を得ている。

TOMLの記述

 今述べたように、TOMLでは基本的に「キー=値」として、構成要素を記述する(加えて「テーブル」という形式もあるが、これは実質的にはインラインテーブルを分かりやすく展開した記述方法だと考えられる)。

キー

 キーには以下の3種類がある。

  • ベアキー:キー=値の組みで、キーを剥き出しで表記しているもの
  • クオート付きキー:キーをダブルクオートまたはシングルクオートで囲んだもの。空白文字を含むキーなどを記述可能
  • ドット付きキー:キーの構成要素をドットで分割する。ドットで分割されたそれぞれがキーとなる

 以下にベアキーの例を示す。なお、以下ではPythonの文字列としてTOMLを記述したものをtomllib.loads関数でパースする例を示していく。

data = """
myname = "deep insider"  #
myint = 1
myfloat = 1.0
myboolean = true  # 偽値はfalse
mydatetime = 2023-12-12T05:00:00.0000+09:00  # オフセット付き
mylocaldatetime = 2023-12-12 05:00:00.0000  # Tの代わりに空白文字も可
mylocaldate = 2023-12-12
mylocaltime = 05:00:00.0000
"""

d = loads(data)

for k, v in d.items():
    print(f'{k}: {v}, type: {type(v)}')
# 表示結果は省略


 冒頭で示した例では特に言及しなかったが、ここで使われているのは全てベアキーである。キーとは別の話題になってしまうが、一度定義したキーは再定義できない点には注意されたい。

data = """
a = 'A'
a = 'AA'  # tomllib.TOMLDecodeError:既存のキーの値は上書きできない
"""

d = loads(data)


 ここではキーaの値を変更しようとしているが、TOMLではキーやテーブルの再定義は禁じられている。

 以下はクオート付きキーの例だ。

data = """
"my name" = "deep insider"
"my boolean" = false
'my date' = 2023-12-12
"""

d = loads(data)

for k, v in d.items():
    print(f'{k}: {v}, type: {type(v)}')
# 出力結果:
#my name: deep insider, type: <class 'str'>
#my boolean: False, type: <class 'bool'>
#my date: 2023-12-12, type: <class 'datetime.date'>


 ドット付きキーの例を以下に示す。

data = """
foo.bar = 'BAR'  # Pythonでは{'foo': {'bar': 'BAR'}}という辞書になる
"""

d = loads(data)  # Pythonでは{'foo': {'bar': 'BAR'}}という辞書になる

print(d)  # {'foo': {'bar': 'BAR'}}


 先ほどは「ドットで分割されたそれぞれがキーとなる」と述べたが、上の結果から分かる通り、「foo.bar」をドットで区切った左側の「foo」がキーとなって、「bar = 'BAR'」が辞書「{'bar': 'BAR'}」となっている(ドットで区切られた「bar」もキーになっている)。

 1つのキーの中でドット付きキーとクオート付きキーを混在させる(foo."bar.baz"など)ことも可能だ。この場合はクオートで囲まれた範囲は1つのキーとなる。

 上では1つのキーを複数回定義できないと述べたが、ドット付きキーを使ったときにはこれが分かりにくくなる場合がある。以下に例を示す。

data = """
x = 0
x.y = 1  # tomllib.TOMLDecodeError:キーxの値を上書きしようとしている
"""

d = loads(data)  # tomllib.TOMLDecodeError


 上のコードではまずキーxの値が0であるとしている。次に、キーx.yの値が1であるとしているつもりだが、これはキーxの値が「{'y': 1}」であるとしているのと同様だ。そのため、例外が発生する。ドット付きキーを使うときには、こうした点に注意が必要だ。

文字列

 文字列はダブルクオートかシングルクオートで囲むことで示すが、両者には違いがある。

  • ダブルクオート:基本文字列を示す。制御文字などはエスケープが必要。複数行の文字列はトリプルクオート(3つのダブルクオート)で記述する
  • シングルクオート:リテラル文字列を示す。記述した文字はその通りに解釈される(バックスラッシュなども)。リテラル文字列中にはエスケープシーケンスを書けない点には注意が必要

 以下に簡単な例を示す。

data = """
dqstr = "c:\\\\windows"
sqstr = 'c:\windows'
"""

d = loads(data)
print(d['dqstr'])  # c:\windows
print(d['sqstr'])  # c:\windows


 ダブルクオートを使用する基本文字列では、バックスラッシュをエスケープ表記するための文字列をさらにエスケープ表記するために上のような書き方になってしまうが、シングルクオートを使用するリテラル文字列ではシンプルに記述できる。ただし、後者ではエスケープシーケンスは使えない(よって、リテラル文字列中でシングルクオートを使いたければ、シングルクオートによるトリプルクオートを使うことになる)。こうした部分を理解して使い分ける必要があるだろう。

 この他にも、文字列を複数行に分けて記述する際には行末にバックスラッシュを置くことで、次の行の先頭にある空白文字が無視されるなどの機能がある(コードを見やすく整形するときに便利だろう)。詳細については公式ドキュメントの「文字列」を参照されたい。

 整数や浮動小数点数の扱いについては公式ドキュメントの「整数」などを参照されたい。なお、浮動小数点数値をfloat以外の型に変換する際には例えば「load(f, parse_float=decimal.Decimal)」などのようにする。

配列

 配列は角かっこ「[]」の中に、その要素をカンマで区切って並べていく。tomllib.load関数/tomllib.loads関数ではこれはリストに変換される。以下に例を示す。

data = """
myarray = ['foo', 'bar', 'baz']  # {'myarray': ['foo', 'bar', 'baz']}という辞書
"""

d = loads(data)
print(d)  # {'myarray': ['foo', 'bar', 'baz']}


 この例では、キーmyarrayの値が文字列を要素とするリストになっている。

 配列の配列も問題なく記述できる。

data = """
myarray2 = [['foo', 'bar'], ['FOO', 'BAR']]
"""

d = loads(data)
print(d)  # {'myarray2': [['foo', 'bar'], ['FOO', 'BAR']]}


インラインテーブル

 インラインテーブルとは波かっこ「{}」の中に「キー = 値」の組みをカンマで区切って並べたものだ。tomllibではインラインテーブルは辞書に変換される。以下に例を示す。

data = """
inline_tbl = {'foo' = 'FOO', 'bar' = 'BAR'}
"""

d = loads(data)
print(d)  # {'inline_tbl': {'foo': 'FOO', 'bar': 'BAR'}}


テーブル

 テーブルは角かっこ「[]」の中にテーブルの名前を記述して、その下に「キー = 値」の組みを記述していく。このとき、テーブル名が大本のキーになり、その下にあるキー/値の組みの集合がその値となる。次のテーブルが始まるか、ファイルが終了するまでの内容が全てそのテーブルの値となる。以下に例を示す。

data = """
[mytbl]  # 辞書のキー'mytbl'
foo = 'FOO'  # テーブルの下のキー/値の組みはテーブル名のキーの値となる
bar = 'BAR'
"""

d = loads(data)
print(d)  # {'mytbl': {'foo': 'FOO', 'bar': 'BAR'}}


 この場合、「mytbl」というテーブル名が大本のキーとなり、その下にある2つのエントリが辞書の内容となって、このキーの値となる。

 このテーブルはドット付きキーを使って以下のように記述したものと同等だ。

data = """
mytbl.foo = 'FOO'
mytbl.bar = 'BAR'
"""

d = loads(data)
print(d)  # {'mytbl': {'foo': 'FOO', 'bar': 'BAR'}}


 シンプルに書けるのであればドット付きキーやインラインテーブルを使い、設定項目が多数にわたるときにはテーブルを使用するというのが基本的な使い分けの指針となるだろう。

 テーブルの名前がキーとなるということは、通常のキーと同様、同じテーブルを何回も定義できないということだ。以下に例を示す。

data = """
[tbl0]
item0 = 'ITEM0'

[tbl0]  # tomllib.TOMLDecodeError:テーブルtbl0の再定義
item1 = 'ITEM1'
"""

d = loads(data)


 この例では「tbl0」というテーブル(キー)を複数行に分けて書こうと思っているのだが、そうしたことはTOMLでは許されていない。

 以下の例も一見すると分からないかもしれないが、テーブルを再定義しようとしているために例外が発生する。

data = """
[tbl1]
subtbl.baz = 'BAZ'  # tbl1の下のサブテーブルsubtblにキーbazを定義

[tbl1.subtbl]  # tomllib.TOMLDecodeError:サブテーブルtbl1.subtblの再定義
hoge = 'HOGE'
"""

d = loads(data)


 最初の「[tbl1]」の定義では「subtbl.baz = 'BAZ'」とすることで、'tbl1'をキーとする値として、'subtbl'をキーとして{'baz': 'BA'}を値とする辞書が作成されている({'tbl1': {'subtbl': {'baz': 'BAZ'}}})。その上で今度はこのsubtblを再定義しようとしているので例外となる。

 ただし、あるテーブル(辞書)にサブテーブルを追加することは許されている。

data = """
[tbl2]
subtbl1.foo = 'foo'  # テーブルtbl2にサブテーブルsubtbl1を追加

[tbl2.subtbl2]  # テーブルtbl2にサブテーブルsubtbl2を追加
bar = 'BAR'
"""

d = loads(data)
print(d)  # {'tbl2': {'subtbl1': {'foo': 'foo'}, 'subtbl2': {'bar': 'BAR'}}}


 この例では、サブテーブルsubtbl1とは別のサブテーブルsubtbl2を追加しているので問題なくパースできる。

テーブルの配列

 テーブルの配列とは、あるキーに複数の辞書(テーブル)を要素とする配列を割り当てたものだ。特定のキーに複数の要素をまとめるときに使える。テーブルの配列は角かっこを二重にして「[[]]」の中にその名前を記述する。以下に例を示す。

data = """
[[products]]
name = 'windows'
version = '10'

[[products]]
name = 'windows'
version = '11'

[[products]]
name = 'macOS'
version = 'ventura'
"""

d = loads(data)


 この例ではproductsの下に、OSの種別ごとに辞書が含まれる。jsonモジュールのdumps関数を使って整形して、これを表示してみよう。

import json

print(json.dumps(d, indent=2))
# 出力結果:
#{
#  "products": [
#    {
#      "name": "windows",
#      "version": "10"
#    },
#    {
#      "name": "windows",
#      "version": "11"
#    },
#    {
#      "name": "macOS",
#      "version": "ventura"
#    }
#  ]
#}


 さらにテーブルの配列をネストさせることも可能だ。インデントはなくてもよいが、分かりやすさを考慮すると付けた方がよいだろう。出力結果は省略する。

data = """
[[departments]]
name = 'editors'

  [[departments.accounts]]
  name = 'isshiki'
  id = 99

  [[departments.accounts]]
  name = 'kawasaki'
  id = 100

[[departments]]
name = 'development'

  [[departments.accounts]]
  name = 'shimada'
  id = 20
"""

d = loads(data)
print(json.dumps(d, indent=2))


 この他にもTOMLファイルを記述したり、それを読み込んだりする際に注意する点がある。それらについては、TOMLのドキュメントを参照されたい。

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

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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