0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python `default_factory` を徹底解説!

0
Posted at

dataclass の“罠”を回避し、実践で使えるパターンを大量紹介 ―

@dataclass を使っていると、必ず登場するのが default_factory
「なんとなく list の初期値をつけるために使う」と理解している方も多いですが、実はもっと奥が深く、高度な初期化ロジック・安全なミュータブル管理・ID生成・依存性注入 まで幅広く応用できます。

この記事では、default_factory本質から理解し、実務で使いこなすための実例をどこよりも丁寧に 紹介します。


❖ 目次

  1. default_factory とは?
  2. なぜ default= ではダメなのか
  3. 基本例(list / dict / set)
  4. 関数を使った初期化
  5. ラムダを使った柔軟な初期化
  6. クラス生成を使うパターン
  7. 日付や UUID を使うパターン
  8. 依存性注入(DI)の初期化
  9. 環境変数・設定読み込みと併用する
  10. キャッシュ・シングルトンとの組み合わせ
  11. まとめ

1. default_factory とは?

field(default_factory=...)「インスタンスを作るたびに呼ばれる関数」 を指定する仕組みです。

field(default_factory=list)

なら「インスタンス生成時に list() を実行して初期値を作る」という意味。


2. なぜ default= ではダメなのか?

可変オブジェクトを初期値にすると、インスタンス間で共有される という致命的な罠があります。

@dataclass
class Bad:
    values: list[int] = []  # ❌ 危険

a = Bad()
b = Bad()

a.values.append(1)
print(b.values)  # → [1](共有されてしまう!)

default_factory を使うべき理由

@dataclass
class Good:
    values: list[int] = field(default_factory=list)

a = Good()
b = Good()

a.values.append(1)
print(b.values)  # → [](完全に分離)

default_factory新品のオブジェクトを毎回作る 工場(factory)の役割を持ちます。


3. 基本例(list / dict / set)

list

items: list[int] = field(default_factory=list)

dict

config: dict[str, str] = field(default_factory=dict)

set

tags: set[str] = field(default_factory=set)

これが最もよく見る基本パターンです。


4. 関数を使った初期化(中級)

処理をカスタマイズしたいときは関数を渡せます。

def initial_permissions():
    return {"read": True, "write": False}

@dataclass
class User:
    permissions: dict = field(default_factory=initial_permissions)

5. ラムダを使った柔軟な初期化

関数を書くほどでもない場合は lambda が便利。

@dataclass
class Config:
    ports: list[int] = field(default_factory=lambda: [80, 443])

6. クラス生成を使うパターン(オブジェクト初期化)

default_factory は「関数」なので、もちろんクラス呼び出しも使えます。

class Logger:
    def __init__(self):
        self.buffer = []

@dataclass
class Service:
    logger: Logger = field(default_factory=Logger)

インスタンスごとに独立した Logger が生成されます。


7. 日付や UUID を使うパターン(実務でよく使う)

UUID

import uuid

@dataclass
class Record:
    id: str = field(default_factory=lambda: str(uuid.uuid4()))

タイムスタンプ

from datetime import datetime

@dataclass
class Job:
    created_at: datetime = field(default_factory=datetime.utcnow)

8. 依存性注入(DI)に使う(上級)

default_factory は軽量 DI コンテナとして使えます。

class ApiClient:
    ...

def create_client():
    return ApiClient(base_url="https://api.example.com")

@dataclass
class Repository:
    client: ApiClient = field(default_factory=create_client)

テスト時だけ client を差し替えることも簡単。


9. 環境変数・設定読み込みと組み合わせる

import os

def load_mode():
    return os.getenv("APP_MODE", "dev")

@dataclass
class Config:
    mode: str = field(default_factory=load_mode)

「起動時に自動で環境変数を読む」仕組みを簡単に導入できます。


10. キャッシュ・シングルトンとの併用

グローバルシングルトンを返す factory も作れます。

class Cache:
    ...

cache_instance = Cache()

@dataclass
class Service:
    cache: Cache = field(default_factory=lambda: cache_instance)

全インスタンスで同じキャッシュを共有したい場面に便利。


✔ 応用:読み込みの重い設定をキャッシュする

config_cache = None

def load_config_once():
    global config_cache
    if config_cache is None:
        print("Loading config...")
        config_cache = {"mode": "prod"}
    return config_cache

@dataclass
class App:
    config: dict = field(default_factory=load_config_once)

11. まとめ

やりたいこと 書き方 メリット
ミュータブルな初期値 default_factory=list 共有バグを防ぐ
デフォルト設定を作りたい 関数 or lambda を使う 柔軟な初期化
ランダム値・UUID を生成 default_factory=lambda: ... 実務で超便利
オブジェクト注入 factory にクラスを渡す DIとして使える
設定の遅延読み込み factory 内でキャッシュ管理 高パフォーマンス

🚀 結論

default_factory は単なる「list の初期値」ではありません。
安全性、可読性、柔軟性を強化し、dataclass を“本物の設計ツール”へ格上げしてくれる存在 です。

実務では:

  • 設定管理
  • Web アプリ
  • API クライアント
  • バッチ処理
  • DI コンテナ
  • 構成オブジェクト生成

など、ほぼすべての層で役立ちます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?