Python 3.11で追加されたtomllibモジュールでTOMLファイルを読み込む方法と、TOMLに関する基本をまとめた。
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
Python 3.11ではtomllibモジュールによるTOML(Tom's Obvious Minimal Language)サポートが追加された。ここでは、tomllibモジュールを使ったTOMLファイルの読み込みと、TOMLについて簡単に説明する。なお、tomllibモジュールではTOMLの書き出しについてはサポートされていない(これについては別稿で取り扱う予定だ)。
TOMLは「ミニマルな構成ファイルフォーマットであることを目指して」おり、「明快なセマンティクスを持ち、読みやすい」ことが特徴となっている。tomllibモジュールでTOMLファイルまたはTOMLフォーマットで記述された文字列をパースすると、辞書が生成される。
tomllibモジュールは以下の2つの関数を提供する。
例えば、次のような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では基本的に「キー=値」として、構成要素を記述する(加えて「テーブル」という形式もあるが、これは実質的にはインラインテーブルを分かりやすく展開した記述方法だと考えられる)。
キーには以下の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}」であるとしているのと同様だ。そのため、例外が発生する。ドット付きキーを使うときには、こうした点に注意が必要だ。
文字列はダブルクオートかシングルクオートで囲むことで示すが、両者には違いがある。
以下に簡単な例を示す。
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のドキュメントを参照されたい。
Copyright© Digital Advantage Corp. All Rights Reserved.