メインコンテンツへスキップ

🧱 シングルトンは「モジュール」で作る

NIP

🧭 はじめに(What)

このページでは、Python におけるシングルトンの定石を整理する。 結論は明確で、Python ではシングルトンを「クラス」で作らない。 代わりに、モジュールそのものをシングルトンとして使うのが最も自然で、読みやすく、テストしやすい。


🎯 ねらい

  • GoF 的な Singleton パターンを Python に持ち込む誤りを正す
  • Python が本来備えている 「唯一性」の表現方法を理解する
  • 過剰設計を避け、意図が一目で伝わる設計を身につける

🧱 問題の背景

Python でシングルトンを実装しようとすると、次のような設計に陥りがちだ。

  • クラス + __new__ オーバーライド
  • メタクラスを使った Singleton
  • スレッドセーフ性を理由に lock だらけの実装

これらは一見「ちゃんとしている」ように見えるが、Python では本質的に不要であることが多い。

Python では import は1回だけ評価され、以降は同じモジュールオブジェクトが再利用される という仕様がある。


❌ よくある誤解

🧬 __new__ をオーバーライドすべき?

  • GoF の定義に忠実であろうとする発想
  • しかし Python では 意図が読み取りづらい
  • 実装を見ないと「シングルトンである」ことが分からない

🧠 メタクラス Singleton は高度で安全?

  • 高度だが、読む側の認知負荷が高すぎる
  • チーム開発では「知らないと読めないコード」になる

🧵 スレッドセーフにしないと危険?

  • import 自体は CPython ではスレッドセーフ
  • ほとんどの用途(設定・キャッシュ)では問題にならない

スレッドセーフ性を理由に 先に複雑化するのは典型的な過剰設計


💥 悪い例:クラスベース Singleton

class ConfigSingleton:
    _instance = None
    _lock = Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
        return cls._instance

問題点

  • 読まないと意図が分からない
  • テスト時の差し替えが面倒
  • 「唯一性」以外の情報が多すぎる

この種の実装は GoF の文脈を知らないと理解できない コードになりやすい。


✅ 良い例①:モジュールをそのまま使う

# config.py
DB_HOST = 'localhost'
DB_PORT = 5432
# usage.py
import config

print(config.DB_HOST)

なぜこれで十分か

  • import config は一度しか評価されない
  • すでに 「唯一のインスタンス」 が保証されている
  • 読めば一発で意図が分かる

Python では モジュール = 自然な Singleton


✅ 良い例②:遅延生成が必要な場合

# resource.py
_resource = None

def get_resource():
    global _resource
    if _resource is None:
        _resource = create_resource()
    return _resource
  • 初期化コストが重い場合に有効
  • 依存関係を関数境界に閉じ込められる

✅ 良い例③:lru_cache(maxsize=1) を使う

from functools import lru_cache

@lru_cache(maxsize=1)
def get_config():
    return load_config()
  • 明示的に「1回だけ生成する」ことが分かる
  • テストでは cache_clear() でリセット可能

「一度だけ生成される関数」 という意図がコードから直接読める。


🧠 設計の要点

  • Python では モジュールが言語組み込みの Singleton
  • 唯一性は import の仕組みが保証している
  • 「クラスである必然性」がない限り、クラスは不要

✅ チェックリスト

  • クラスである必要は本当にあるか?
  • import 1回で目的を満たせないか?
  • テスト時に差し替え・初期化し直せるか?
  • 読んだ人が「これは Singleton だ」と即理解できるか?

🔚 まとめ

Python においてシングルトンを「設計パターン」として実装しようとすると、 かえって Python らしさを損なう

  • GoF 的 Singleton → 避ける
  • モジュール / 関数 + cache → 推奨

シンプルで、読めて、差し替えられる。 それが Python における正しい「唯一性」の設計である。