閉じる

Pythonで設定ファイルを読み込むならJSON?YAML?実用サンプル完全ガイド

Pythonでアプリケーションを作ると、データベース接続情報やAPIキー、ログレベルなど、コードから切り離して管理したい「設定」が必ず出てきます。

こうした設定をJSONやYAMLのファイルとして外出ししておくと、デプロイや環境切り替えが格段に楽になります。

本記事では、Pythonでの設定ファイル管理をJSONとYAML両方から丁寧に解説し、実務でそのまま使えるサンプルを詳しく紹介します。

Pythonで設定ファイルを扱う基礎

Pythonで設定ファイル(JSON・YAML)を使うメリット

設定値をPythonコードに直接ベタ書きする方法は最初は手軽ですが、規模が少し大きくなると途端に不便になります。

そこでJSONやYAMLなどの設定ファイルを使うと、次のような利点があります。

まず設定をコードから分離できるので、ステージング用・本番用など環境ごとに設定ファイルだけを差し替えれば、同じコードで異なる環境にデプロイできます。

たとえばconfig.prod.yamlconfig.dev.yamlを用意して切り替える運用が代表例です。

また再デプロイせずに設定を変更できるのも重要です。

ログレベル、タイムアウト値、リトライ回数などを設定ファイルにしておけば、アプリケーションを落とさずに再読み込みするだけで調整できます。

この仕組みは長期運用されるWebアプリやバッチ処理で特に有効です。

さらに設定の見通しが良くなり、共有しやすくなるという点も見逃せません。

設定をファイルとして切り出すことで、READMEとあわせて「このファイルを編集すれば動きます」と説明しやすくなり、チーム開発でも新規メンバーが入りやすくなります。

JSONやYAMLは多くの言語・ツールでサポートされているため、他のサービスやインフラツールと連携しやすいのも利点です。

JSON・YAML・INI・TOMLの違いとPythonでの向き不向き

設定ファイルといえばJSONやYAMLがよく挙げられますが、実はINIやTOMLも含めた4つが代表的な形式です。

それぞれの特徴とPythonとの相性を整理しておきます。

まずJSONは言語非依存かつ広くサポートされているデータ交換フォーマットです。

Python標準ライブラリのjsonで扱うことができ、辞書やリストはそのままシリアライズ・デシリアライズできます。

構造がシンプルでパーサも高速ですが、標準仕様上コメントを許していない点と、書き方がやや冗長になる点が弱点です。

YAMLは人間が読み書きしやすいことを重視したフォーマットで、インデントによる階層表現、アンカー・エイリアスによる再利用など、表現力が豊富です。

Kubernetesなどインフラ設定でも広く使われています。

ただしパーサの実装によってはセキュリティリスクがあるため、Pythonではyaml.safe_loadのような安全な読み込みが推奨されます。

INI形式はWindowsの設定ファイルでおなじみの古くからある形式で、セクションとキー・バリューのペアだけを扱うシンプルな構造です。

Python標準ライブラリのconfigparserで読み書きできますが、ネストした構造や配列を表現するのは苦手です。

TOMLはINIに近い書き心地ながら、型や階層構造を適切に表現できる新しい形式です。

Python 3.11以降ではtomllibとして標準ライブラリに取り込まれました。

特にPythonのパッケージ設定(pyproject.toml)で事実上の標準となっているため、Pythonエコシステムとの親和性は高いです。

代表的な項目を表にすると次のようになります。

形式Python標準対応コメントネスト/配列読みやすさ主な用途
JSON○(json)×(仕様外)△(記号多め)APIレスポンス、設定、データ交換
YAML×(外部ライブラリ)○(とても柔軟)○(インデントベース)インフラ設定、複雑なアプリ設定
INI○(configparser)△(実質フラット)△(単純だが制限多い)レガシー設定、簡易設定
TOML○(3.11〜 tomllib)pyproject.toml、モダンな設定

Pythonで新規に設定管理を設計する場合、昨今はJSON・YAML・TOMLのいずれかを選ぶことがほとんどです。

INIは既存システムとの互換性がある場合を除けば、あえて新規採用する必然性は下がっています。

小規模・個人開発でおすすめの設定ファイル形式

小規模や個人開発では、過度に複雑な形式はかえって負担になります。

そのため「誰が触るか」「どのツールと連携するか」という観点で選ぶと失敗しにくくなります。

フロントエンド(JavaScript)や他のサービスと設定を共有したい場合は、JSONを選ぶとツール対応が最も広くて扱いやすいです。

VSCodeなどのエディタにはJSONスキーマを使った補完機能もあり、IDEサポートも充実しています。

一方で、設定が複雑になりそうで、かつチーム内にYAMLの経験者がいる場合はYAMLを選ぶとファイルが読みやすく保守しやすくなる傾向があります。

特にKubernetesなどYAML文化に慣れている場合は、その流れに乗るのが自然です。

TOMLはPythonプロジェクトと親和性が非常に高く、個人開発でも扱いやすい新しめの選択肢です。

ただし本記事のテーマはJSONとYAMLなので、ここでは概要紹介に留め、以降はJSONとYAMLに絞って詳しく解説していきます。

PythonでJSON設定ファイルを読み込む

JSON設定ファイルの基本構造と書き方

JSONの設定ファイルは、基本的に1つのオブジェクト(キーと値の組の集合)として記述することが多いです。

Pythonで扱うときは辞書(dict)に直結するイメージです。

JSONで使える主な値の種類は、文字列・数値・真偽値・null・配列・オブジェクトです。

設定ファイルでは、APIキーやURLは文字列、ポート番号やリトライ回数は数値、機能のオンオフは真偽値で表現することが一般的です。

たとえば、簡単な設定ファイルconfig.jsonは次のように書けます。

JSON
{
  "app_name": "sample_app",
  "debug": true,
  "port": 8000,
  "database": {
    "host": "localhost",
    "port": 5432,
    "user": "app_user",
    "password": "secret"
  }
}

このようにネストしたオブジェクトを用いることで、論理的なまとまりごとに設定をグルーピングできます。

たとえばdatabase以下にデータベース関連設定を集約する、といった使い方です。

json標準ライブラリでの読み込み・書き込みサンプル

Pythonでは標準ライブラリのjsonモジュールだけでJSON設定を読み書きできます。

追加インストールは不要です。

JSONファイルを読み込む例

Python
# config_loader_json.py
import json
from pathlib import Path

def load_config(path: str) -> dict:
    """JSON形式の設定ファイルを読み込んで辞書として返す関数"""
    config_path = Path(path)

    # ファイルの存在チェック
    if not config_path.exists():
        raise FileNotFoundError(f"設定ファイルが見つかりません: {config_path}")

    # 文字コードはUTF-8を推奨
    with config_path.open("r", encoding="utf-8") as f:
        # json.loadでファイルから直接読み込む
        config = json.load(f)

    return config

if __name__ == "__main__":
    # 同じディレクトリにあるconfig.jsonを読み込む例
    config = load_config("config.json")

    # 設定値の参照例
    print("アプリ名:", config.get("app_name"))
    print("デバッグ:", config.get("debug"))
    print("DBホスト:", config.get("database", {}).get("host"))

想定されるconfig.jsonを用意しておけば、上記スクリプトは次のような出力になります。

実行結果
アプリ名: sample_app
デバッグ: True
DBホスト: localhost

JSONファイルへ書き込む例

Python
# config_writer_json.py
import json
from pathlib import Path

def save_config(path: str, config: dict) -> None:
    """辞書オブジェクトをJSON形式で保存する関数"""
    config_path = Path(path)

    # 親ディレクトリが存在しない場合は作成
    config_path.parent.mkdir(parents=True, exist_ok=True)

    # ensure_ascii=False と indent=2 で
    # ・日本語をそのまま保存
    # ・見やすい整形(インデント)を有効化
    with config_path.open("w", encoding="utf-8") as f:
        json.dump(config, f, ensure_ascii=False, indent=2)

if __name__ == "__main__":
    config_data = {
        "app_name": "sample_app",
        "debug": False,
        "port": 8080,
        "features": {
            "use_cache": True,
            "enable_metrics": True
        }
    }

    save_config("output_config.json", config_data)
    print("設定ファイルをoutput_config.jsonとして保存しました。")

実行するとoutput_config.jsonに次のようなJSONが出力されます。

JSON
{
  "app_name": "sample_app",
  "debug": false,
  "port": 8080,
  "features": {
    "use_cache": true,
    "enable_metrics": true
  }
}

ネスト構造・配列を含むJSON設定の実用例

実務では、複数サービスのエンドポイントや、複数環境の設定を配列で扱うケースが多くあります。

たとえば外部APIクライアントの設定は、次のようにネスト・配列を組み合わせると整理しやすくなります。

JSON
{
  "logging": {
    "level": "INFO",
    "file": "logs/app.log"
  },
  "services": [
    {
      "name": "user_service",
      "base_url": "https://api.example.com/users",
      "timeout_sec": 5,
      "retries": 3
    },
    {
      "name": "payment_service",
      "base_url": "https://api.example.com/payments",
      "timeout_sec": 10,
      "retries": 5
    }
  ]
}

この設定をPythonで読み込み、サービス一覧を処理するコードは次のように書けます。

Python
# services_config_example.py
import json
from pathlib import Path

def load_services_config(path: str) -> list[dict]:
    """services配列を取り出して返すユーティリティ関数"""
    with Path(path).open("r", encoding="utf-8") as f:
        config = json.load(f)

    # "services"キーが存在しない場合に備えて空リストをデフォルトに
    services = config.get("services", [])
    return services

if __name__ == "__main__":
    services = load_services_config("services_config.json")

    # サービスごとの設定を一覧表示
    for svc in services:
        name = svc.get("name")
        url = svc.get("base_url")
        timeout = svc.get("timeout_sec")
        retries = svc.get("retries")
        print(f"{name}: {url} (timeout={timeout}, retries={retries})")

想定されるservices_config.jsonが前述のJSONであれば、出力は次のようになります。

実行結果
user_service: https://api.example.com/users (timeout=5, retries=3)
payment_service: https://api.example.com/payments (timeout=10, retries=5)

このようにネストと配列を活用すると、複数の類似設定を一括管理しやすくなり、コード側もループでシンプルに処理できるようになります。

Pythonオブジェクトへの変換と型の扱い

JSONをPythonで扱う時に押さえておきたいのは、JSONの各型がどのPython型にマッピングされるかです。

基本的な対応は次の通りです。

JSONの型Pythonの型
string"abc"str
number123, 3.14intまたはfloat
booleantrue, falsebool
nullnullNone
array[1, 2, 3]list
object{"a": 1}dict

たとえば、次のJSONを読み込んだ場合を考えます。

JSON
{
  "port": 8000,
  "debug": true,
  "allowed_hosts": ["localhost", "example.com"],
  "extra": null
}

Pythonで読み込むと次のような型になります。

Python
# json_types_example.py
import json

data = json.loads("""
{
  "port": 8000,
  "debug": true,
  "allowed_hosts": ["localhost", "example.com"],
  "extra": null
}
""")

print(data, type(data))
print("port:", data["port"], type(data["port"]))
print("debug:", data["debug"], type(data["debug"]))
print("allowed_hosts:", data["allowed_hosts"], type(data["allowed_hosts"]))
print("extra:", data["extra"], type(data["extra"]))

出力結果は次のようになります。

実行結果
{'port': 8000, 'debug': True, 'allowed_hosts': ['localhost', 'example.com'], 'extra': None} <class 'dict'>
port: 8000 <class 'int'>
debug: True <class 'bool'>
allowed_hosts: ['localhost', 'example.com'] <class 'list'>
extra: None <class 'NoneType'>

JSONには日付型やバイナリ型などは存在しないため、文字列として表現するか、独自のルールを決めて解釈する必要があります

そのような場合は、後述するpydanticなどのバリデーションライブラリを併用すると型安全に扱えます。

JSON設定でのコメント・日本語・文字コードの注意点

JSONは本来コメントをサポートしていない仕様です。

そのため// コメント/* コメント */を入れると、標準的なJSONパーサではエラーになります。

設定にコメントを残したい場合は、次のように"_comment"などの専用キーを設けて表現するのが現実的な折衷案です。

JSON
{
  "_comment": "デバッグ用フラグ。本番環境では必ずfalseにすること。",
  "debug": true,
  "port": 8000
}

日本語については、Pythonのjson.dumpはデフォルトではensure_ascii=Trueとなっており、日本語を\uXXXX形式にエスケープします。

ログチェックなどでそのまま日本語を読みたい場合は、先ほどの例のようにensure_ascii=Falseを指定します。

設定ファイルの文字コードはUTF-8に統一するのが最も無難です。

なお、コメントをどうしても使いたい場合、拡張JSON(JSON5など)や独自パーサを採用する手もありますが、他のツールとの互換性が落ちるデメリットも大きいため、一般的なJSON設定では避けることをおすすめします。

PythonでYAML設定ファイルを読み込む

YAMLの特徴とJSONとの違い

YAMLは人間が読み書きすることを最優先に設計されたデータ記述フォーマットです。

インデントにより階層を表現する点ではPythonの構文とも似ており、慣れると非常に読みやすくなります。

代表的な特徴をJSONと対比しながら整理すると次の通りです。

  • 括弧({})やカンマ,を多用するJSONに対し、YAMLはインデントと改行で構造を表現します。
  • コメントとして# 〜が使えるため、設定ファイル中に説明を豊富に書くことができます。
  • アンカーとエイリアス&*により、同じ設定ブロックの再利用が可能です。
  • JSONは仕様が比較的単純で実装が揃っていますが、YAMLは仕様が複雑で、パーサの実装差や安全でない機能が存在する点に注意が必要です。

たとえば、次のJSONとYAMLは等価な設定です。

JSON
{
  "app_name": "sample_app",
  "debug": true,
  "port": 8000,
  "database": {
    "host": "localhost",
    "port": 5432
  }
}
YAML
app_name: sample_app
debug: true
port: 8000
database:
  host: localhost
  port: 5432

YAML側は波括弧や引用符が少なく、行数も少ないため、複雑な設定になるほど視認性の差が出てきます

PyYAMLのインストールと基本的な読み込みサンプル

PythonでYAMLを扱うには、標準ライブラリではなく外部ライブラリのPyYAMLを利用します。

まずはインストールから確認します。

Shell
pip install pyyaml

インストール後、基本的な読み込みは次のように行います。

Python
# config_loader_yaml.py
from pathlib import Path
import yaml  # PyYAML

def load_yaml_config(path: str) -> dict:
    """YAML形式の設定ファイルを読み込んで辞書として返す関数"""
    config_path = Path(path)

    if not config_path.exists():
        raise FileNotFoundError(f"設定ファイルが見つかりません: {config_path}")

    with config_path.open("r", encoding="utf-8") as f:
        # 安全な読み込みにはsafe_loadを使用
        config = yaml.safe_load(f)

    return config

if __name__ == "__main__":
    config = load_yaml_config("config.yaml")

    print("アプリ名:", config.get("app_name"))
    print("デバッグ:", config.get("debug"))
    print("DBホスト:", config.get("database", {}).get("host"))

YAMLの設定config.yamlが次の内容だとします。

YAML
app_name: sample_app
debug: true
database:
  host: localhost
  port: 5432

実行すると次のような出力になります。

実行結果
アプリ名: sample_app
デバッグ: True
DBホスト: localhost

JSONの例と非常によく似た構成であることが分かりますが、コメントを自由に書けることや、アンカーなどの拡張表現を使えることがYAMLの利点です。

safe_loadとfull_loadの違いとセキュリティ

PyYAMLには複数の読み込み関数がありますが、実務で必ず押さえておきたいのがsafe_loadfull_loadの違いです。

yaml.safe_loadは、YAMLに含まれる値をPythonの基本的な型(辞書・リスト・文字列・数値・真偽値・Noneなど)に限定して読み込みます。

任意のPythonオブジェクトを生成しないため、安全性が高いのが特徴です。

一方、yaml.full_loadは、YAMLにタグを埋め込むことで任意のPythonオブジェクトを生成できる仕組みをサポートしており、悪意あるYAMLを読み込むと任意コード実行につながる危険性があります。

そのため、外部から渡されるYAMLをfull_loadで読み込むのは避けるべきです。

特別な理由がない限り、設定ファイル用途ではyaml.safe_loadを使うと覚えておくとよいでしょう。

YAMLの高度なタグ機能を活用したい場合でも、まずは安全性を優先することが重要です。

階層構造・アンカー・エイリアスを使ったYAML設定例

YAMLの強力な機能として、アンカー&とエイリアス*による設定の再利用があります。

これにより、環境ごとの設定差分をスッキリ表現することができます。

たとえば、データベース設定のベースを定義し、開発環境と本番環境で一部だけ変更したいケースを考えます。

YAML
# db_config.yaml

# ベースとなるDB設定をアンカーで定義
default_db: &default_db
  host: localhost
  port: 5432
  user: app_user
  password: secret

# 開発環境のDB設定
development:
  <<: *default_db   # default_dbの内容をマージ
  database: app_dev

# 本番環境のDB設定
production:
  <<: *default_db
  host: db.prod.internal
  database: app_prod
  # 本番だけユーザー名とパスワードを変更
  user: app_prod_user
  password: super_secret

このYAMLをPythonで読み込むと、アンカーとエイリアスは展開(解決)された状態の辞書として得られます。

Python
# yaml_anchor_example.py
import yaml
from pprint import pprint
from pathlib import Path

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

print("development設定:")
pprint(config["development"])

print("\nproduction設定:")
pprint(config["production"])

出力結果は次のようになります。

実行結果
development設定:
{'database': 'app_dev',
 'host': 'localhost',
 'password': 'secret',
 'port': 5432,
 'user': 'app_user'}

production設定:
{'database': 'app_prod',
 'host': 'db.prod.internal',
 'password': 'super_secret',
 'port': 5432,
 'user': 'app_prod_user'}

このようにアンカーとエイリアスを使うと、重複を最小限に保ちながら環境ごとの差分だけを明示的に記述できるため、設定ファイルの保守性が大幅に向上します。

型・日付・環境ごとの設定をYAMLで表現する

YAMLはJSONよりも多くの型表現をサポートしています。

たとえば、日付や日時をYAMLで表現すると、自動的にPythonのdatetimeオブジェクトに変換される場合があります。

次のようなYAMLを考えます。

YAML
# app_settings.yaml
app_name: sample_app
start_date: 2024-01-01
maintenance:
  enabled: false
  schedule_at: 2024-02-01T10:00:00
  message: "定期メンテナンスのお知らせ"

これをyaml.safe_loadで読み込んだ場合、実装やバージョンによってはstart_dateschedule_atdatetime.datedatetime.datetimeとして扱われることがあります。

そのためYAMLの自動型解釈の挙動は、プロジェクトで統一して理解しておくと安心です。

環境ごとの設定は、先ほどのアンカー例以外にも、環境名をトップレベルキーにして切り替える方法がよく使われます。

YAML
# env_config.yaml

development:
  debug: true
  db_url: postgresql://dev_user:dev_pass@localhost/dev_db

staging:
  debug: false
  db_url: postgresql://stg_user:stg_pass@stg-db.internal/stg_db

production:
  debug: false
  db_url: postgresql://prod_user:prod_pass@prod-db.internal/prod_db

Python側では、環境名を引数や環境変数で受け取り、該当するキーだけを利用します。

Python
# env_config_loader.py
import os
from pathlib import Path
import yaml

def load_env_config(path: str, env: str) -> dict:
    """環境名(env)に対応する設定を返す"""
    with Path(path).open("r", encoding="utf-8") as f:
        all_config = yaml.safe_load(f)

    if env not in all_config:
        raise KeyError(f"環境 '{env}' の設定が見つかりません。定義されているのは: {list(all_config.keys())}")

    return all_config[env]

if __name__ == "__main__":
    # 環境変数APP_ENVから環境名を取得。なければdevelopmentをデフォルトに。
    env = os.environ.get("APP_ENV", "development")
    config = load_env_config("env_config.yaml", env)

    print(f"現在の環境: {env}")
    print("debug:", config["debug"])
    print("db_url:", config["db_url"])

このパターンは、後述する環境変数との組み合わせとも相性がよく、YAMLによる環境別設定管理の代表的なスタイルです。

実用サンプルで比較するJSON vs YAML設定

Webアプリの設定例(APIキー・DB接続情報)をJSONとYAMLで比較

具体的なWebアプリケーションの設定を、JSON版とYAML版で比較してみます。

ここではAPIキーやDB接続情報、ログ設定などを含む例を扱います。

JSON版(config.json)

JSON
{
  "app": {
    "name": "my_web_app",
    "debug": false,
    "host": "0.0.0.0",
    "port": 8000
  },
  "logging": {
    "level": "INFO",
    "file": "logs/app.log"
  },
  "database": {
    "url": "postgresql://app_user:secret@db:5432/app_db",
    "pool_size": 10,
    "max_overflow": 5
  },
  "external_api": {
    "base_url": "https://api.example.com",
    "api_key": "YOUR_API_KEY_HERE",
    "timeout_sec": 5
  }
}

YAML版(config.yaml)

YAML
app:
  name: my_web_app
  debug: false
  host: 0.0.0.0
  port: 8000

logging:
  level: INFO
  file: logs/app.log

database:
  url: postgresql://app_user:secret@db:5432/app_db
  pool_size: 10
  max_overflow: 5

external_api:
  base_url: https://api.example.com
  api_key: YOUR_API_KEY_HERE
  timeout_sec: 5

両者は意味的には同一ですが、YAMLは括弧や引用符が少ないため、一見して構造を把握しやすいことが分かります。

一方でJSONは機械にとって扱いやすく、ほぼ全ての言語・ツールでサポートされているという利点があります。

Pythonでの読み込みコードは、JSONとYAMLでほとんど変わりません。

Python
# web_config_loader.py
import json
import yaml
from pathlib import Path

def load_json_config(path: str) -> dict:
    with Path(path).open("r", encoding="utf-8") as f:
        return json.load(f)

def load_yaml_config(path: str) -> dict:
    with Path(path).open("r", encoding="utf-8") as f:
        return yaml.safe_load(f)

if __name__ == "__main__":
    json_conf = load_json_config("config.json")
    yaml_conf = load_yaml_config("config.yaml")

    print("[JSON] DB URL:", json_conf["database"]["url"])
    print("[YAML] DB URL:", yaml_conf["database"]["url"])

想定出力は次のようになります。

実行結果
[JSON] DB URL: postgresql://app_user:secret@db:5432/app_db
[YAML] DB URL: postgresql://app_user:secret@db:5432/app_db

このようにPython側から見れば、読み込み後はどちらも単なる辞書として扱えるため、主に「編集する人」と「連携するツール」の観点で選び分ければ十分です。

開発/ステージング/本番の環境別設定の管理パターン

環境別の設定管理にはいくつかのパターンがあります。

代表的なものを整理すると、次の3つです。

1つ目は単一ファイルで環境ごとのキーを分けるパターンです。

先ほどのYAML例のように、トップレベルにdevelopmentproductionなどのキーを置き、Python側で必要な環境だけ選択して使う形です。

ファイルが1つなので管理しやすい一方、環境が増えると1ファイルが大きくなりがちです。

2つ目はファイルを環境ごとに分割するパターンです。

たとえばconfig.development.yamlconfig.production.yamlのように分け、それぞれデプロイ時に必要なファイルだけを配置します。

環境ごとの差異が大きい場合には分かりやすい方法です。

3つ目はベース設定+環境別差分ファイルのパターンです。

共通部分をconfig.base.yamlにまとめ、環境ごとの差分をconfig.dev.yamlconfig.prod.yamlに書き分け、読み込み時にマージします。

YAMLならアンカーとエイリアス、JSONならPython側でマージ処理を書いて実現できます。

状況に応じてどれを採用するかは変わりますが、小規模なうちは1ファイル方式、大きくなってきたらベース+差分方式のように、段階的に移行していく運用も現実的です。

.envファイルや環境変数との組み合わせ方

APIキーやDBパスワードのような機密情報は、設定ファイルに直接書かず、環境変数や.envファイルで管理するのが一般的なベストプラクティスです。

理由は、設定ファイルがGitリポジトリに含まれていると、誤って秘密情報をコミットしてしまう危険があるためです。

典型的な役割分担としては、次のような方針がよく採用されます。

  • JSON/YAMLの設定ファイルには、環境変数名やプレースホルダだけを書く
  • 実際の値は環境変数や.envファイルで管理し、起動時にアプリケーションが読み込む

たとえば、YAMLではこう書きます。

YAML
# config.yaml
database:
  url_env: DATABASE_URL  # 実際のURLは環境変数DATABASE_URLから取得
  pool_size: 10

external_api:
  base_url: https://api.example.com
  api_key_env: EXTERNAL_API_KEY

Python側ではos.environから実際の値を取得します。

Python
# config_with_env.py
import os
import yaml
from pathlib import Path

def load_config(path: str) -> dict:
    with Path(path).open("r", encoding="utf-8") as f:
        return yaml.safe_load(f)

def resolve_env_values(config: dict) -> dict:
    """*_envキーを実際の環境変数の値に置き換える簡易処理"""
    db_env_key = config["database"]["url_env"]
    api_env_key = config["external_api"]["api_key_env"]

    config["database"]["url"] = os.environ.get(db_env_key)
    config["external_api"]["api_key"] = os.environ.get(api_env_key)

    return config

if __name__ == "__main__":
    config = load_config("config.yaml")
    config = resolve_env_values(config)

    print("DB URL:", config["database"]["url"])
    print("API KEY:", config["external_api"]["api_key"])

.envファイルを使う場合は、python-dotenvのようなライブラリで読み込むこともできます。

Shell
pip install python-dotenv
Python
# dotenv_example.py
from dotenv import load_dotenv
import os

# .envファイルから環境変数を読み込む
load_dotenv()

print("DATABASE_URL:", os.environ.get("DATABASE_URL"))

このように構造的で非秘密な情報はJSON/YAML、秘密情報は環境変数と役割を分けることで、安全かつ柔軟な設定管理が可能になります。

JSONとYAMLの選び方

JSONとYAMLはどちらも強力ですが、プロジェクトに応じて向き不向きがあります。

選択の目安を整理すると次のようになります。

JSONを選ぶとよいケースは、フロントエンドや他言語のサービスと設定を共有したい場合や、すでに周辺ツールがJSON前提で動いている場合です。

また、コメントが不要で、単純にデータ構造を外出ししたいだけならJSONで十分なことが多いです。

YAMLを選ぶとよいケースは、人間が設定ファイルを頻繁に編集する場合や、設定が多段ネスト・複数環境・複数サービスにまたがるような大規模なものになる場合です。

コメントを書き込みたい場合や、アンカーによる再利用で重複を減らしたい場合もYAMLが向いています。

また、チームの経験値も重要な要素です。

YAMLに慣れているメンバーが多ければYAMLを選んだ方が生産性は高くなりますし、逆にYAMLのインデントルールに不慣れでトラブルが多い場合はJSONのほうが堅実です。

まとめると、次のように整理できます。

  • 他ツールとの互換性・機械処理主体 → JSON
  • 人が日常的に編集・複雑な設定 → YAML

迷った場合でも、小規模なうちはどちらを選んでも後から移行しやすいので、まずはチームが扱いやすい方から始めるのがおすすめです。

設定ファイルのバリデーション(pydantic・jsonschema・schema)活用例

設定ファイルは柔軟である一方、誤った値や不足した必須項目があっても、そのままアプリケーションが動き始めてしまうリスクがあります。

そこで、読み込み後にスキーマ(型定義)に基づいて検証する仕組みを導入すると、起動時に問題を検出できるようになります。

Pythonでよく使われるライブラリとしては、次のようなものがあります。

  • pydantic: Pythonの型ヒントとクラスを使ってスキーマを定義できる。FastAPIでも採用されている。
  • jsonschema: JSON Schema仕様に基づいてJSONデータを検証する。
  • schema: シンプルなPython的APIでバリデーションルールを定義できる軽量ライブラリ。

ここでは、型付きクラスと相性が良いpydanticを用いた例を紹介します。

Shell
pip install pydantic
Python
# pydantic_config_example.py
from pathlib import Path
import yaml
from pydantic import BaseModel, AnyHttpUrl, ValidationError

class DatabaseConfig(BaseModel):
    url: str
    pool_size: int = 10
    max_overflow: int = 5

class ExternalAPIConfig(BaseModel):
    base_url: AnyHttpUrl
    api_key: str
    timeout_sec: int = 5

class AppConfig(BaseModel):
    app_name: str
    debug: bool = False
    database: DatabaseConfig
    external_api: ExternalAPIConfig

def load_config(path: str) -> AppConfig:
    """YAMLを読み込み、AppConfigとしてバリデーションして返す"""
    with Path(path).open("r", encoding="utf-8") as f:
        raw = yaml.safe_load(f)

    # AppConfigのスキーマに沿って検証・型変換
    return AppConfig(**raw)

if __name__ == "__main__":
    try:
        config = load_config("config.yaml")
    except ValidationError as e:
        # スキーマに合わない場合はここで詳細なエラーが出る
        print("設定ファイルの内容が不正です。")
        print(e)
    else:
        print("アプリ名:", config.app_name)
        print("DB URL:", config.database.url)
        print("APIベースURL:", config.external_api.base_url)
        print("タイムアウト:", config.external_api.timeout_sec)

正常なconfig.yamlであれば、次のような出力が得られます。

実行結果
アプリ名: sample_app
DB URL: postgresql://app_user:secret@db:5432/app_db
APIベースURL: https://api.example.com
タイムアウト: 5

一方、たとえばexternal_api.base_urlを不正なURL文字列に変更すると、次のようなバリデーションエラーが表示されます。

実行結果
設定ファイルの内容が不正です。
1 validation error for AppConfig
external_api.base_url
  value is not a valid URL (type=value_error.url)

このように設定ファイルをクラスとスキーマで厳密に扱うことで、運用中に気付きにくい設定ミスを起動時に検出でき、信頼性が大きく向上します。

JSONであっても、YAMLであっても、読み込んだ後のPythonオブジェクトに対して同様のバリデーションを適用できます。

まとめ

本記事では、Pythonで設定ファイルを扱う際のJSONとYAMLの基本から実務レベルの設計・運用パターンまでをまとめて紹介しました。

JSONは標準ライブラリで扱えてツール互換性に優れ、YAMLは人間が読み書きしやすく複雑な設定に向いています。

環境別設定や.envとの併用、pydanticなどによるバリデーションを組み合わせれば、設定周りのトラブルを大幅に減らせます。

プロジェクト規模やチームのスキルに合わせてJSONとYAMLを選び分け、「コードから設定をきちんと切り離した、変更に強いPythonアプリケーション」を構築していきましょう。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!