Pythonで設定ファイルを読み込むには、iniなら configparser、TOMLなら tomllib(3.11以降)、JSONなら json という標準ライブラリを使い、YAMLは PyYAML、.env は python-dotenv を追加して読みます。 形式によって「読み込んだ値の型」が大きく違い、ini と .env はすべて文字列、TOML・YAML・JSON は数値や真偽値、リストとして受け取れます。

この記事では、同じ設定内容を5つの形式で書き分け、読み込みコードと取得結果を型つきで並べます。そのうえで、各形式でつまずきやすい点を実際のエラー文とともに示し、どれを選ぶかの指針をまとめます。掲載した出力は Python 3.11.15、PyYAML 6.0.3、python-dotenv 1.2.2、tomli-w 1.2.0 で実際に動かした結果です。

設定ファイル形式の早見表

比べる点 ini TOML YAML JSON .env
読み込むライブラリ configparser tomllib PyYAML(yaml) json python-dotenv
標準ライブラリか 標準 標準(3.11以降) 外部(pip install pyyaml) 標準 外部(pip install python-dotenv)
コメント 書ける(; と #) 書ける(#) 書ける(#) 書けない 書ける(#)
読み込んだ値の型 すべて str str / int / float / bool / 日付 / list / dict str / int / float / bool / 日付 / list / dict / None str / int / float / bool / list / dict / None すべて str
ネスト(入れ子) セクション1段のみ できる(テーブル) できる(インデント) できる できない
リスト ない(自分で分割) ある ある ある ない(自分で分割)
書き込み 標準でできる 読み込みのみ。書き込みは tomli-w できる(safe_dump) できる(json.dump) できる(set_key)
書き込み時にコメントが残るか 残らない 残らない(tomli-w) 残らない ― 残る
主な使いどころ 単純なキーと値、昔からあるツール Python のツール設定、アプリの設定 ネストの深い設定、インフラ系ツール プログラム同士の受け渡し 環境ごとに変わる値、秘密の値

迷ったら、Python 3.11 以降で新しく作る設定は TOML が扱いやすい選択です。 標準ライブラリで読めて、コメントが書けて、型がそのまま受け取れます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python設定ファイルの読み込み|ini・TOML・YAML・JSON比較」

同じ設定を5つの形式で書き分ける

題材は、アプリ名・デバッグの有無・ポート番号・タイムアウト秒数・許可するホストの一覧と、データベースの接続先です。

ini(config.ini)

; アプリの設定(iniのコメントは ; か #)
[app]
name = inventory
debug = true
port = 8080
timeout = 2.5
allowed_hosts = localhost, example.com

[database]
host = db.example.com
port = 5432

TOML(config.toml)

# アプリの設定
[app]
name = "inventory"
debug = true
port = 8080
timeout = 2.5
allowed_hosts = ["localhost", "example.com"]
started = 2026-10-03

[database]
host = "db.example.com"
port = 5432

YAML(config.yaml)

# アプリの設定
app:
  name: inventory
  debug: true
  port: 8080
  timeout: 2.5
  allowed_hosts:
    - localhost
    - example.com
  started: 2026-10-03

database:
  host: db.example.com
  port: 5432

JSON(config.json)

{
  "app": {
    "name": "inventory",
    "debug": true,
    "port": 8080,
    "timeout": 2.5,
    "allowed_hosts": ["localhost", "example.com"]
  },
  "database": {
    "host": "db.example.com",
    "port": 5432
  }
}

.env

# アプリの設定
APP_NAME=inventory
APP_DEBUG=true
APP_PORT=8080
APP_TIMEOUT=2.5
APP_ALLOWED_HOSTS=localhost,example.com
DATABASE_HOST=db.example.com
DATABASE_PORT=5432

見た目の違いをまとめると次のとおりです。

形式 文字列の引用符 階層の表し方 リストの書き方
ini 付けない [セクション] カンマ区切りの文字列にして自分で分割
TOML 必ず付ける [テーブル] ["a", "b"]
YAML 付けなくてよい インデント(スペース) - a を並べる
JSON 必ず二重引用符 { } の入れ子 ["a", "b"]
.env 付けなくてよい ない(名前に接頭辞を付ける) カンマ区切りの文字列にして自分で分割

5形式を読み込んで、値と型を横並びで比べる

# read_all.py
# 事前に: pip install pyyaml python-dotenv
import configparser
import json
import tomllib

import yaml
from dotenv import dotenv_values


def show(label, value):
    print(f"{label:5} {value!r:28} {type(value).__name__}")


ini = configparser.ConfigParser()
ini.read("config.ini", encoding="utf-8")

with open("config.toml", "rb") as f:
    toml = tomllib.load(f)

with open("config.yaml", encoding="utf-8") as f:
    yml = yaml.safe_load(f)

with open("config.json", encoding="utf-8") as f:
    js = json.load(f)

env = dotenv_values(".env")

for key in ("name", "debug", "port", "timeout", "allowed_hosts"):
    print(f"--- {key}")
    show("ini", ini["app"][key])
    show("toml", toml["app"][key])
    show("yaml", yml["app"][key])
    show("json", js["app"][key])
    show(".env", env["APP_" + key.upper()])

print("--- started")
show("toml", toml["app"]["started"])
show("yaml", yml["app"]["started"])
python read_all.py
--- name
ini   'inventory'                  str
toml  'inventory'                  str
yaml  'inventory'                  str
json  'inventory'                  str
.env  'inventory'                  str
--- debug
ini   'true'                       str
toml  True                         bool
yaml  True                         bool
json  True                         bool
.env  'true'                       str
--- port
ini   '8080'                       str
toml  8080                         int
yaml  8080                         int
json  8080                         int
.env  '8080'                       str
--- timeout
ini   '2.5'                        str
toml  2.5                          float
yaml  2.5                          float
json  2.5                          float
.env  '2.5'                        str
--- allowed_hosts
ini   'localhost, example.com'     str
toml  ['localhost', 'example.com'] list
yaml  ['localhost', 'example.com'] list
json  ['localhost', 'example.com'] list
.env  'localhost,example.com'      str
--- started
toml  datetime.date(2026, 10, 3)   date
yaml  datetime.date(2026, 10, 3)   date

結果を表にまとめます。

設定 ini TOML YAML JSON .env
name str str str str str
debug str('true') bool bool bool str('true')
port str('8080') int int int str('8080')
timeout str('2.5') float float float str('2.5')
allowed_hosts str list list list str
started(日付) ― date date 日付型がない ―

ini と .env は、すべての値が文字列で返ります。 数値や真偽値として使うには変換が必要です。TOML・YAML・JSON はファイルに書いた型のまま受け取れます。日付を型として持てるのは TOML と YAML です。

読み込みコードの要点を1行ずつにすると、次のようになります。

形式 読み込みコード ファイルを開くモード
ini c = configparser.ConfigParser(); c.read(path, encoding="utf-8") パスを渡す(自分で開かない)
TOML with open(path, "rb") as f: data = tomllib.load(f) バイナリ("rb")
YAML with open(path, encoding="utf-8") as f: data = yaml.safe_load(f) テキスト
JSON with open(path, encoding="utf-8") as f: data = json.load(f) テキスト
.env values = dotenv_values(path) または load_dotenv(path) パスを渡す

configparserでiniを読むときの3つのポイント

値が全部文字列になる → getint・getboolean で変換する

上の結果のとおり、ini["app"]["port"] は '8080' という文字列です。特に真偽値は気づきにくく、次のようになります。

import configparser

ini = configparser.ConfigParser()
ini.read("config.ini", encoding="utf-8")

print(bool(ini["app"]["debug"]))   # 'true' という文字列
print(bool("false"))               # 'false' でも結果は同じ
True
True

空でない文字列は、中身が "false" でも真と評価されます。 型つきで受け取るメソッドを使うと解決します。

app = ini["app"]
print(app.getboolean("debug"), app.getint("port"), app.getfloat("timeout"))
print([h.strip() for h in app["allowed_hosts"].split(",")])
print(app.getint("retries", fallback=3))   # キーがないときの既定値
True 8080 2.5
['localhost', 'example.com']
3
メソッド 返す型 受け付ける値
get(key) / section[key] str そのまま
getint(key) int 整数として読める文字列
getfloat(key) float 小数として読める文字列
getboolean(key) bool 1 / yes / true / on と 0 / no / false / off(大文字小文字は区別しない)
get(key, fallback=値) str または fallback キーがないとき fallback を返す

変換できない値が入っていると、その場でエラーになります。

ValueError: Not a boolean: maybe
ValueError: invalid literal for int() with base 10: '80a'

キーやセクションがないときのエラーは、取り出し方で変わります。

書き方 キーがないとき
ini["app"]["missing"] KeyError: 'missing'
ini.get("app", "missing") NoOptionError: No option 'missing' in section: 'app'
ini.get("nosec", "x") NoSectionError: No section: 'nosec'
ini.get("app", "missing", fallback="none") 'none' を返す

もう1つ知っておくと役立つのが、存在しないファイルを read() してもエラーにならないことです。

c = configparser.ConfigParser()
print(c.read("nothing.ini"), c.sections())
[] []

read() は読み込めたファイル名のリストを返すので、空リストかどうかを確かめると、パスの間違いに早く気づけます。

%を含む値で補間エラーになる → %% と書くか interpolation=None

configparser には、%(名前)s と書くと同じセクションの別の値を埋め込む補間という機能があります。そのため、日付の書式のように % を含む値を書くとエラーになります。

; pct.ini
[log]
format = %Y-%m-%d %H:%M
rate = 50%
import configparser

c = configparser.ConfigParser()
c.read("pct.ini")
print(c["log"]["format"])
InterpolationSyntaxError: '%' must be followed by '%' or '(', found: '%Y-%m-%d %H:%M'

rate = 50% を取り出したときも同じ種類のエラーです。

InterpolationSyntaxError: '%' must be followed by '%' or '(', found: '%'

エラーは読み込み時ではなく、値を取り出した時に出ます。 直し方は2つあります。

# 直し方1:補間を使わないなら、機能を止める
c = configparser.ConfigParser(interpolation=None)
c.read("pct.ini")
print(c["log"]["format"], c["log"]["rate"])
%Y-%m-%d %H:%M 50%
# 直し方2:補間を使いたいなら、ファイル側で % を %% と書く
c = configparser.ConfigParser()
c.read_string("""
[log]
format = %%Y-%%m-%%d

[paths]
base = /var/app
logs = %(base)s/logs
""")
print(c["log"]["format"], c["paths"]["logs"])
%Y-%m-%d /var/app/logs
状況 選ぶ方法
補間を使っていない ConfigParser(interpolation=None)
%(base)s のような埋め込みを使っている 文字としての % を %% と書く
そもそも記号を多く含む値を置きたい TOML など補間のない形式に移す

キーが小文字になる → optionxform = str

import configparser

c = configparser.ConfigParser()
c.read_string("""
[App]
UserName = taro
API_KEY = abc
""")
print(c.sections(), list(c["App"].keys()))
print(c["App"]["username"], c["App"]["USERNAME"])
['App'] ['username', 'api_key']
taro taro

キー(オプション名)は小文字に変換されて保存されます。 取り出すときも小文字にしてから探すので、USERNAME でも取れます。一方、セクション名は大文字小文字を区別するので、c["app"] は KeyError: 'app' になります。

キーの大文字小文字を保ちたいときは、読み込む前に optionxform を差し替えます。

c = configparser.ConfigParser()
c.optionxform = str
c.read_string("[App]\nUserName = taro\nAPI_KEY = abc\n")
print(list(c["App"].keys()))
['UserName', 'API_KEY']

そのほかの仕様と、iniへの書き込み

仕様 実行結果
[DEFAULT] に書いた値は全セクションから見える [DEFAULT] timeout = 10 があると、dict(c["app"]) は {'name': 'a', 'timeout': '10'}
同じセクションに同じキーを2回書く DuplicateOptionError: While reading from '<string>' [line 3]: option 'name' in section 'app' already exists
文字列以外を代入する c["app"]["retries"] = 3 は TypeError: option values must be strings

書き込みは標準でできます。値は文字列にして代入します。

import configparser

c = configparser.ConfigParser()
c.read("config.ini", encoding="utf-8")
c["app"]["port"] = "9090"
c["app"]["retries"] = str(3)
c["cache"] = {"enabled": "false"}

with open("out.ini", "w", encoding="utf-8") as f:
    c.write(f)
[app]
name = inventory
debug = true
port = 9090
timeout = 2.5
allowed_hosts = localhost, example.com
retries = 3

[database]
host = db.example.com
port = 5432

[cache]
enabled = false

元のファイルの1行目にあったコメントは、書き出したファイルには残りませんでした。人がコメントを書き込むファイルをプログラムで上書きするときは、この点を踏まえて設計します。

tomllibでTOMLを読むときの3つのポイント

"rb" で開かないと TypeError になる

import tomllib

with open("config.toml") as f:      # テキストモード
    data = tomllib.load(f)
TypeError: File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`

encoding="utf-8" を付けても同じです。TOML は仕様で UTF-8 と決まっているので、tomllib がバイト列から自分で解釈します。open(path, "rb") に直すと読めます。

関数 渡すもの
tomllib.load(f) "rb" で開いたファイル
tomllib.loads(s) 文字列(str)

文字列を持っている場合は loads を使います。

with open("config.toml", encoding="utf-8") as f:
    data = tomllib.loads(f.read())
print(data["database"])
{'host': 'db.example.com', 'port': 5432}

書き込みは tomli-w を追加する

tomllib は読み込み専用です。公開されている名前を確かめると、3つだけでした。

import tomllib

print([n for n in dir(tomllib) if not n.startswith("_")])
tomllib.dump
['TOMLDecodeError', 'load', 'loads']
AttributeError: module 'tomllib' has no attribute 'dump'

書き出すには、外部ライブラリの tomli-w を使います。

# 事前に: pip install tomli-w
import tomllib

import tomli_w

with open("config.toml", "rb") as f:
    data = tomllib.load(f)

data["app"]["port"] = 9090
data["cache"] = {"enabled": False, "ttl": 300}

with open("out.toml", "wb") as f:      # 書き込みもバイナリ
    tomli_w.dump(data, f)
[app]
name = "inventory"
debug = true
port = 9090
timeout = 2.5
allowed_hosts = [
    "localhost",
    "example.com",
]
started = 2026-10-03

[database]
host = "db.example.com"
port = 5432

[cache]
enabled = false
ttl = 300
つまずく点 実際のメッセージ 直し方
"w" で開いて tomli_w.dump TypeError: write() argument must be str, not bytes "wb" で開く。または tomli_w.dumps(data) で文字列を得る
値に None が入っている TypeError: Object of type 'NoneType' is not TOML serializable TOML には null がないので、キーごと省く
コメントが消える 元の1行目のコメントは出力に残らなかった コメントを保ちたい場合は tomlkit などの専用ライブラリを検討(この記事では未実行)

TOMLの書き方でエラーになる例

TOML は型をはっきり書く形式なので、書き方が違うとその場で教えてくれます。

書いた内容 結果 直し方
name = inventory TOMLDecodeError: Invalid value (at line 1, column 8) 文字列は "inventory" と引用符で囲む
debug = True TOMLDecodeError: Invalid value (at line 1, column 9) 真偽値は小文字で true
port = 08 TOMLDecodeError: Expected newline or end of document after a statement (at line 1, column 9) 先頭の 0 は付けない。8 と書く
n = null TOMLDecodeError: Invalid value (at line 1, column 5) null はない。キーを書かない
同じキーを2回書く TOMLDecodeError: Cannot overwrite a value (at end of document) 片方を消す

エラーに行と列が出るので、直す場所がすぐ分かります。ネストや配列の書き方も確かめました。

[[servers]]
name = "a"

[[servers]]
name = "b"

[app.log]
level = "INFO"
inline = { x = 1, y = 2 }
{'servers': [{'name': 'a'}, {'name': 'b'}], 'app': {'log': {'level': 'INFO', 'inline': {'x': 1, 'y': 2}}}}

[[名前]] は「辞書のリスト」、[a.b] は入れ子の辞書になります。Windows のパスのように \ を含む値は、'C:\Users\taro' と一重引用符で囲むとそのまま読めました。

Python 3.10 以前では tomllib がないので、同じ使い方ができる外部ライブラリ tomli を入れて import tomli as tomllib と書く方法が広く使われています(この記事では未実行)。

YAMLは yaml.load ではなく safe_load を使う

safe_load を使う理由

YAML には、タグという仕組みで「この値は Python のこのオブジェクトとして作る」と指示する書き方があります。PyYAML の制限のないローダーは、この指示に従って Python の関数を呼び出します。害のない os.getcwd(今いるフォルダを返す関数)で確かめました。

import os

import yaml

doc = "!!python/object/apply:os.getcwd []"

# 制限のないローダー:YAML に書かれた関数が実行される
print(yaml.load(doc, Loader=yaml.UnsafeLoader) == os.getcwd())

# safe_load:読み込みを止めてエラーにする
yaml.safe_load(doc)
True
ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object/apply:os.getcwd'
  in "<unicode string>", line 1, column 1:
    !!python/object/apply:os.getcwd []
    ^

1つ目の True は、YAML を読み込んだだけで関数が実行されたことを示しています。設定ファイルは、人から受け取ったり、ダウンロードしたりすることもあります。safe_load は文字列・数値・リスト・辞書といった基本の型だけを作るので、こうした指示が入っていても実行せずにエラーで知らせてくれます。設定ファイルの読み込みには yaml.safe_load を使う、と決めておくと安心です。

Loader=yaml.FullLoader でも、この例は同じ ConstructorError になりました。なお、PyYAML 6 では Loader を省いた yaml.load(f) はそのままでは動きません。

yaml.load(open("config.yaml", encoding="utf-8"))
TypeError: load() missing 1 required positional argument: 'Loader'
書き方 動き 使いどころ
yaml.safe_load(f) 基本の型だけを作る 設定ファイルの読み込み(これを使う)
yaml.load(f, Loader=yaml.SafeLoader) safe_load と同じ 同上
yaml.load(f) PyYAML 6 では TypeError ―
yaml.load(f, Loader=yaml.UnsafeLoader) YAML 内の指示で Python の関数を呼べる 設定ファイルには使わない

YAMLで型が思ったとおりにならない例

YAML は引用符を省けるぶん、値の型を自動で決めます。次の内容を safe_load で読んだ結果です。

country: NO
enabled: yes
switch: on
version: 1.10
zip: 0123
time: 12:30
empty:
quoted: 'NO'
ver2: '1.10'
{'country': False, 'enabled': True, 'switch': True, 'version': 1.1, 'zip': 83, 'time': 750, 'empty': None, 'quoted': 'NO', 'ver2': '1.10'}
書いた値 読み込んだ結果 理由 文字列にしたいとき
NO False(bool) yes / no / on / off は真偽値として扱われる 'NO' と引用符で囲む
1.10 1.1(float) 小数として解釈される '1.10'
0123 83(int) 先頭が 0 の数字は8進数として解釈される '0123'
12:30 750(int) 60進数として解釈される '12:30'
(空) None 値がないと null になる ''

バージョン番号・コード・時刻のように「文字列として扱いたい値」は、引用符で囲むと確実です。

インデントの乱れもよくあるつまずきです。

書き方 エラー
インデントにタブを使う ScannerError: while scanning for the next token / found character '\t' that cannot start any token
インデントの幅がそろっていない ScannerError: mapping values are not allowed here

同じキーを2回書いた場合はエラーにならず、後に書いた値が使われました(a: 1 と a: 2 で {'a': 2})。

YAMLへの書き込み

import yaml

with open("config.yaml", encoding="utf-8") as f:
    data = yaml.safe_load(f)

data["app"]["port"] = 9090
print(yaml.safe_dump(data, allow_unicode=True, sort_keys=False))
app:
  name: inventory
  debug: true
  port: 9090
  timeout: 2.5
  allowed_hosts:
  - localhost
  - example.com
  started: 2026-10-03
database:
  host: db.example.com
  port: 5432
引数 効果
allow_unicode=True 日本語をそのまま出力する。付けないとエスケープ表記になる({"名前": "在庫"} で確認)
sort_keys=False キーを元の順番のまま出力する。既定ではアルファベット順に並べ替える

コメントは出力に残りません。

JSONを設定ファイルに使うときのポイント

JSON は標準ライブラリで読み書きでき、ほぼすべての言語が対応しています。設定ファイルとして使うときは、コメントが書けないことと書式が厳密なことを押さえておきます。

書いた内容 エラー 直し方
{"a": 1,}(末尾のカンマ) JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 9 (char 8) 最後のカンマを消す
{'a': 1}(一重引用符) JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1) 二重引用符にする
{"a": 1} // memo(コメント) JSONDecodeError: Extra data: line 1 column 10 (char 9) コメントを消す。説明は別のキーやドキュメントに書く
{"a": True}(Python の書き方) JSONDecodeError: Expecting value: line 1 column 7 (char 6) true / false / null と小文字で書く

同じキーを2回書くと、YAML と同じく後の値が使われました({"a": 1, "a": 2} は {'a': 2})。

書き込みは json.dump です。

import json

with open("config.json", encoding="utf-8") as f:
    data = json.load(f)

data["app"]["port"] = 9090

with open("out.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)
引数 効果
ensure_ascii=False 日本語をそのまま出力する({"名前": "在庫"})。付けないとエスケープ表記になる
indent=2 改行とインデントを入れて読みやすくする

日付型はそのまま書き出せません。

TypeError: Object of type date is not JSON serializable

日付は "2026-10-03" のような文字列で持ち、読み込んだ後に date.fromisoformat() で変換します。

.envは環境ごとに変わる値を置く場所

.env は、接続先やAPIキーのように環境ごとに変わる値や、リポジトリに入れたくない値を置くための形式です。python-dotenv で読み込みます。

# 事前に: pip install python-dotenv
import os

from dotenv import dotenv_values, load_dotenv

# 1. 辞書として受け取る(環境変数は変えない)
values = dotenv_values(".env")
print(values["APP_PORT"], type(values["APP_PORT"]).__name__)

# 2. 環境変数に読み込む
print(os.environ.get("APP_PORT"))
print(load_dotenv(".env"))
print(os.environ.get("APP_PORT"))

# 3. 型を変換して使う
port = int(os.environ["APP_PORT"])
debug = os.environ["APP_DEBUG"].lower() in ("1", "true", "yes", "on")
hosts = os.environ["APP_ALLOWED_HOSTS"].split(",")
print(port, debug, hosts)
8080 str
None
True
8080
8080 True ['localhost', 'example.com']
知っておくこと 実行結果
値はすべて文字列 '8080'(str)。int や bool は自分で変換する
既にある環境変数は上書きしない OS 側で APP_NAME=from_os を設定してから load_dotenv() すると from_os のまま
上書きしたいとき load_dotenv(".env", override=True) で .env の値(inventory)になった
ないキーを os.environ["APP_MISSING"] で読む KeyError: 'APP_MISSING'
既定値つきで読む os.environ.get("APP_MISSING", "default") は 'default'
set_key("out.env", "APP_PORT", "9090") で書き換える その行が APP_PORT='9090' になり、1行目のコメントは残った

「OS の環境変数が優先、.env は足りない分を補う」という動きが既定なので、本番環境では環境変数、手元では .env、という使い分けがしやすくなっています。.env ファイルは .gitignore に入れ、キー名だけを書いた .env.example を共有する方法がよく使われます。

どの形式を選ぶかの指針

こんな設定 選ぶ形式 理由
Python 3.11 以降で新しく作るアプリの設定 TOML 標準で読める、コメント可、型がそのまま
pyproject.toml に寄せたいツール設定 TOML Python のツールが共通で使う場所
ネストが深い、リストの中に辞書が並ぶ YAML インデントで構造が見やすい
ほかの言語やツールが既に YAML を読んでいる YAML 既存の仕組みに合わせる
プログラムが書き出し、プログラムが読む JSON どの言語でも標準で読み書きできる
人が手で編集し、説明を添えたい TOML か YAML コメントが書ける
接続先やAPIキーなど環境ごとに変わる値 .env(環境変数) リポジトリに入れずに切り替えられる
キーと値だけの単純な設定、既存の ini がある ini 標準で読み書きできる。型変換は getint などで
外部ライブラリを入れられない ini / JSON / TOML(3.11以降は読み込みのみ) すべて標準ライブラリ
flowchart TD
    A[設定ファイルを決める] --> B{環境ごとに変わる値や秘密の値か}
    B -- はい --> C[.env と環境変数]
    B -- いいえ --> D{人が手で編集するか}
    D -- いいえ --> E[JSON]
    D -- はい --> F{ネストが深いか 既にYAMLを使っているか}
    F -- はい --> G[YAML と safe_load]
    F -- いいえ --> H{Python 3.11以降か}
    H -- はい --> I[TOML と tomllib]
    H -- いいえ --> J[ini と configparser または tomli を追加]

1つに絞る必要はなく、「アプリの設定は TOML、秘密の値は .env」のように組み合わせるのが一般的です。

読み込んだ設定を型つきのクラスで受ける

辞書のまま使うと、キーの打ち間違いに気づくのが実行時になります。dataclass に詰め替えると、エディタの補完が効き、型も明確になります。

import tomllib
from dataclasses import dataclass


@dataclass(frozen=True)
class AppConfig:
    name: str
    debug: bool
    port: int
    timeout: float
    allowed_hosts: list[str]


def load_config(path: str) -> AppConfig:
    with open(path, "rb") as f:
        data = tomllib.load(f)
    app = data["app"]
    return AppConfig(
        name=app["name"],
        debug=app["debug"],
        port=app["port"],
        timeout=app["timeout"],
        allowed_hosts=app["allowed_hosts"],
    )


config = load_config("config.toml")
print(config)
print(config.port + 1)
AppConfig(name='inventory', debug=True, port=8080, timeout=2.5, allowed_hosts=['localhost', 'example.com'])
8081

frozen=True を付けると、読み込んだ後に値を書き換えられなくなり、設定が途中で変わる心配がなくなります。必須のキーがファイルにないときは、app["name"] の行で KeyError になるので、起動時に気づけます。

つまずきやすい点と直し方(形式別まとめ)

形式 起きること 直し方
ini if ini["app"]["debug"]: がいつも真 getboolean("debug") を使う
ini InterpolationSyntaxError(% を含む値) interpolation=None か、%% と書く
ini キーが小文字になる optionxform = str
ini 設定が空のまま動く read() の戻り値が空リストでないか確かめる
TOML TypeError: File must be opened in binary mode open(path, "rb")
TOML module 'tomllib' has no attribute 'dump' tomli-w を使う
TOML Invalid value 文字列を引用符で囲む。真偽値は小文字
YAML NO や 1.10 が別の型になる 引用符で囲む
YAML load() missing 1 required positional argument: 'Loader' yaml.safe_load に変える
YAML タブでインデントして ScannerError スペースでそろえる
JSON 末尾のカンマ、コメントで JSONDecodeError 取り除く
.env 数値のつもりが文字列 int() などで変換する
.env .env を変えたのに値が変わらない 既に環境変数がある。override=True か環境変数を見直す

よくある質問

Q. Python 3.10 以前で TOML を読むにはどうすればよいですか? A. tomllib は 3.11 で標準に入りました。3.10 以前では外部ライブラリの tomli を入れ、import tomli as tomllib と書くと同じ使い方ができることが知られています(この記事では未実行です)。

Q. 設定を Python ファイル(config.py)に書くのはどうですか? A. import するだけで読めて型もそのまま使えるので、小さなスクリプトでは手軽です。一方、設定ファイルがプログラムとして実行されることになるため、利用者が編集するファイルや外から受け取るファイルには、データだけを書ける TOML や YAML が向いています。

Q. コメントを保ったまま設定ファイルを書き換えたいときは? A. この記事で確かめた範囲では、configparser・tomli-w・PyYAML の書き出しではコメントが残らず、python-dotenv の set_key では残りました。TOML なら tomlkit、YAML なら ruamel.yaml といった、コメントを保つことを目的にしたライブラリがあります(この記事では未実行です)。

Q. 複数の設定ファイルを重ねて読みたいときは? A. configparser の read() はファイル名のリストを受け取り、後のファイルの値で上書きします。TOML や YAML では、既定値の辞書を読み込んでから、環境別のファイルの辞書で上書きする形にします。「既定値 → ファイル → 環境変数」の順に優先度を上げていくと、切り替えが分かりやすくなります。

まとめ

  • ini と .env は値がすべて文字列。TOML・YAML・JSON は数値・真偽値・リストをそのまま受け取れる
  • configparser は getint / getboolean で型変換。% を含む値は interpolation=None か %%、キーの小文字化は optionxform = str
  • tomllib は "rb" で開く。読み込み専用なので、書き込みは tomli-w を追加する
  • YAML は yaml.safe_load。NO や 1.10 のような値は引用符で囲むと文字列のまま読める
  • JSON はコメントと末尾カンマが書けない。プログラム同士の受け渡しに向く
  • 環境ごとに変わる値や秘密の値は .env と環境変数へ。既にある環境変数が優先される
  • 迷ったら TOML。ネストが深ければ YAML、機械が読み書きするなら JSON

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python設定ファイルの読み込み|ini・TOML・YAML・JSON比較」

株式会社RJCでは、Pythonの設定ファイルの扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?