Factory Methodは、オブジェクトを作る処理をメソッドに切り出し、どのクラスを生成するかをサブクラスに決めさせるGoFの生成パターンです。ただしPythonはクラスそのものを変数に入れて渡せるので、Javaと同じ継承の形で書かなくて済む場面が多くあります。この記事では、abc.ABCで書くFactory Methodを動くコードで示したうえで、ファクトリ関数、classmethodの代替コンストラクタ、__init_subclass__による自動登録、entry_pointsによるプラグイン読み込みを並べ、どれを選ぶかの基準を示します。掲載コードの出力は、CPython 3.10.20、3.11.15、3.12.13、3.13.14、3.14.6と、正式版前のベータ版3.15.0b4で実行した結果です。
まとめ:PythonでのFactory Methodの要点
- Factory Methodは、生成用のメソッドを基底クラスで宣言し、生成するクラスはサブクラスが決めるパターンです。GoFの本では別名をVirtual Constructorとしています。
- Pythonではabc.ABCと@abstractmethodで生成メソッドを抽象化します。実装し忘れたサブクラスは、インスタンスを作った時点でTypeErrorになります。
- 種類を文字列で選ぶだけならdictで振り分けるファクトリ関数、入力形式ごとに作り方を変えるならclassmethodの代替コンストラクタで足ります。
- 生成クラスを後から増やすなら__init_subclass__で登録し、別パッケージから差し込むならentry_pointsを使います。
- 生成するクラスを差し替えたいだけなら、サブクラスを作らずにクラスを引数で渡すほうが短く、テストもしやすくなります。
Factory Methodパターンの定義と4つの役割
GoF(Gamma、Helm、Johnson、Vlissides)が1994年の『Design Patterns』で示した意図は、「オブジェクトを生成するためのインタフェースを定義し、どのクラスをインスタンス化するかはサブクラスに決めさせる」というものです。生成をサブクラスへ先送りするので、基底クラスのコードには具体クラスの名前が出てきません。
登場する役割は4つです。この記事のコードでは次のクラスが対応します。
| 役割 | 担当すること | 本記事のクラス |
|---|---|---|
| Product | 生成されるオブジェクトの共通の型 | Notifier |
| ConcreteProduct | 実際に生成されるクラス | EmailNotifier、SlackNotifier |
| Creator | 生成メソッドを宣言し、それを使う処理を持つ | AlertService |
| ConcreteCreator | 生成メソッドを実装して具体クラスを返す | EmailAlertService、SlackAlertService |
Creatorの本体は、通知を送るalert()のような業務処理です。生成メソッドはその途中で呼ばれる部品にすぎません。処理の流れを基底クラスに固定し、一部をサブクラスに任せる構造はテンプレートメソッドパターンのものです。この例では、Template Methodに相当するalert()の中からFactory Methodを呼び出しています。
abc.ABCで書くFactory Methodの実装例
障害通知をメールかSlackで送るサービスを例にします。AlertServiceのalert()は、通知手段が何かを知らないまま処理を進め、手段の生成だけをcreate_notifier()に任せます。
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, to: str, body: str) -> str: ...
class EmailNotifier(Notifier):
def send(self, to: str, body: str) -> str:
return f"email to {to}: {body}"
class SlackNotifier(Notifier):
def send(self, to: str, body: str) -> str:
return f"slack to #{to}: {body}"
class AlertService(ABC):
def alert(self, to: str, message: str) -> str:
notifier = self.create_notifier() # ここがFactory Method
return notifier.send(to, f"[ALERT] {message}")
@abstractmethod
def create_notifier(self) -> Notifier: ...
class EmailAlertService(AlertService):
def create_notifier(self) -> Notifier:
return EmailNotifier()
class SlackAlertService(AlertService):
def create_notifier(self) -> Notifier:
return SlackNotifier()
for service in (EmailAlertService(), SlackAlertService()):
print(service.alert("ops", "disk 90%"))
出力は次のとおりです。
email to ops: [ALERT] disk 90%
slack to #ops: [ALERT] disk 90%
AlertServiceのコードにはEmailNotifierもSlackNotifierも登場しません。SMS通知を足すときは、SmsNotifierとSmsAlertServiceを追加するだけで、alert()は書き換えずに済みます。既存コードを変えずに機能を足せるこの性質は、オープンクローズドの原則がいう「拡張に開き、修正に閉じる」状態にあたります。
生成メソッドを実装し忘れたときのTypeError
@abstractmethodを付けたメソッドが残っているクラスは、インスタンスを作れません。create_notifier()を書き忘れたサブクラスで試します。
class BrokenAlertService(AlertService):
pass
BrokenAlertService()
エラーの文面はPython 3.12で変わりました。
# Python 3.10 / 3.11
TypeError: Can't instantiate abstract class BrokenAlertService with abstract method create_notifier
# Python 3.12 〜 3.15
TypeError: Can't instantiate abstract class BrokenAlertService without an implementation for abstract method 'create_notifier'
エラーはcreate_notifier()を呼んだときではなく、インスタンスを作った時点で出ます。実装漏れはサービスの起動時やテストの初期化で見つかるので、本番で通知を送る瞬間まで気づかない事態を避けられます。テストで例外を検証するときは、文面全体ではなく例外の型かクラス名だけを確かめてください。3.11から3.12へ上げたとき、メッセージ全文を照合するテストは落ちます。
ファクトリ関数:関数で生成を振り分ける書き方
ファクトリ関数は、オブジェクトの生成や取得を担い、そのオブジェクトを返す関数のことです。JavaScriptでは、classとnewを使わずにオブジェクトを返す関数をこう呼ぶことが多いですが、Pythonでも同じ意味で使われます。Python公式ドキュメントも、collections.namedtuple()を「factory function for creating tuple subclasses with named fields」と説明しています。
通知手段を文字列で選ぶなら、クラスをdictに入れて引くだけで済みます。Notifierと2つの具体クラスは前のコードと同じです。
NOTIFIERS = {
"email": EmailNotifier,
"slack": SlackNotifier,
}
def create_notifier(kind: str) -> Notifier:
try:
notifier_cls = NOTIFIERS[kind]
except KeyError:
raise ValueError(f"unknown notifier: {kind!r}") from None
return notifier_cls()
print(create_notifier("slack").send("ops", "deploy done"))
create_notifier("sms")
slack to #ops: deploy done
Traceback (most recent call last):
...
ValueError: unknown notifier: 'sms'
Pythonではクラスもオブジェクトなので、dictの値にクラスを入れ、取り出して呼べばインスタンスになります。種類を足すときはNOTIFIERSに1行加えるだけで、if/elifの分岐は増えません。from Noneは、内部で起きたKeyErrorを例外の連鎖表示から隠し、呼び出し側に「未知の種類」という意味のエラーだけを見せるための指定です。
この形は、サブクラスで生成を差し替える構造を持たないので、GoFのFactory Methodではありません。Simple Factoryと呼ばれる書き方で、GoFの23パターンにも含まれていません。それでも設定ファイルやリクエストの値で生成クラスを選ぶ用途なら、ほとんどの場合これで足ります。
ファクトリ関数は毎回新しいオブジェクトを作るとも限りません。logging.getLogger()は公式ドキュメントに「All calls to this function with a given name return the same logger instance.」とあるとおり、同じ名前には同じロガーを返します。生成をコンストラクタの直接呼び出しから関数に移すと、こうしたキャッシュや使い回しを呼び出し側に知らせずに入れられます。
classmethodで作る代替コンストラクタ
同じクラスを、CSVの1行やタイムスタンプなど別の形式の入力から作りたいときは、classmethodを生成メソッドにします。標準ライブラリのdict.fromkeys()、int.from_bytes()、datetime.fromtimestamp()はいずれもこの形で、型の__dict__から取り出すとclassmethod_descriptorになっています。次の例は引用符を含まない2列の入力に限った簡略版です。一般的なCSVの読み取りにはcsv.readerを使ってください。
from datetime import date
class Invoice:
def __init__(self, number: str, issued: date) -> None:
self.number = number
self.issued = issued
@classmethod
def from_csv_row(cls, row: str) -> "Invoice":
number, issued = row.split(",")
return cls(number, date.fromisoformat(issued))
@staticmethod
def parse(row: str) -> "Invoice":
number, issued = row.split(",")
return Invoice(number, date.fromisoformat(issued))
class TaxInvoice(Invoice):
pass
print(type(TaxInvoice.from_csv_row("T-001,2026-09-01")).__name__)
print(type(TaxInvoice.parse("T-002,2026-09-02")).__name__)
TaxInvoice
Invoice
classmethod版はclsを呼ぶので、サブクラスから呼べばサブクラスのインスタンスが返ります。staticmethod版はクラス名Invoiceを直書きしているため、TaxInvoiceから呼んでもInvoiceが返ります。継承される可能性があるクラスの生成メソッドは、staticmethodではなくclassmethodで書いてください。clsの受け取り方やstaticmethodとの違いはPythonの@classmethodの解説で詳しく扱っています。
生成クラスの自動登録:__init_subclass__とentry_points
ファクトリ関数のdictは、種類を足すたびに手で1行書き足す必要があります。ここでは、サブクラスの定義時に登録する方法と、インストール済みパッケージのメタデータから候補を見つける方法を紹介します。
__init_subclass__によるサブクラスの自動登録
__init_subclass__は、そのクラスを継承したサブクラスが定義された時点で呼ばれるフックです。クラス定義の括弧に書いたキーワード引数を受け取れるので、形式名とクラスの対応をここで登録できます。次のCsvExporterは、値にカンマ・改行・引用符が含まれない場合に限定した簡略例です。一般的なCSV出力にはcsv.writerを使ってください。
import json
class Exporter:
registry: dict[str, type["Exporter"]] = {}
def __init_subclass__(cls, *, fmt: str, **kwargs) -> None:
super().__init_subclass__(**kwargs)
if fmt in Exporter.registry:
raise ValueError(f"duplicate exporter format: {fmt!r}")
Exporter.registry[fmt] = cls
@classmethod
def for_format(cls, fmt: str) -> "Exporter":
return cls.registry[fmt]()
def export(self, rows: list) -> str:
raise NotImplementedError
class CsvExporter(Exporter, fmt="csv"):
def export(self, rows: list) -> str:
return "\n".join(",".join(map(str, row)) for row in rows)
class JsonExporter(Exporter, fmt="json"):
def export(self, rows: list) -> str:
return json.dumps(rows)
print(sorted(Exporter.registry))
print(Exporter.for_format("json").export([[1, "a"]]))
['csv', 'json']
[[1, "a"]]
fmtを付け忘れたサブクラスは、定義した時点でTypeError: Exporter.__init_subclass__() missing 1 required keyword-only argument: 'fmt'になります。登録漏れがクラス定義の段階で見つかるので、キーワード専用引数にしておく価値があります。同じfmtを別のクラスが名乗ったときは、import順で選ばれるクラスが入れ替わらないよう、登録時にValueErrorで止めています。
見落としやすいのは、登録がサブクラスのクラス定義を実行したとき、つまりそのモジュールがimportされたときにしか起きない点です。CsvExporterを別モジュールに置くと、基底クラスだけをimportした段階のregistryは空の{}のままで、CsvExporterのモジュールをimportした時点で'csv'が入ります。サブクラスを別ファイルに分けるなら、生成前に対象サブクラスの定義を実行しておく必要があります。importはパッケージの__init__.pyでも、アプリケーションの初期化処理でも構いません。
entry_pointsによる別パッケージの生成クラス読み込み
生成クラスを本体とは別のパッケージで配布したいときは、パッケージのメタデータにあるentry pointsを使います。追加側のパッケージmyapp-csvは、pyproject.tomlとモジュール1つの構成にします。
myapp_csv/pyproject.toml
myapp_csv/src/myapp_csv/__init__.py
pyproject.tomlでは、entry-pointsテーブルにグループ名myapp.exporters、エントリポイント名csv、読み込むクラスの場所を書きます。
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "myapp-csv"
version = "0.1.0"
[project.entry-points."myapp.exporters"]
csv = "myapp_csv:CsvExporter"
src/myapp_csv/__init__.pyには生成されるクラスだけを置きます。
class CsvExporter:
def export(self, rows):
return "\n".join(",".join(map(str, r)) for r in rows)
本体側はimportlib.metadata.entry_points()でグループと名前を指定して取り出し、load()でクラスを得ます。同じグループと名前を複数のパッケージが登録したときの扱いは読み込む側が決めることになるので、この例では候補が2つ以上あればLookupErrorにしています。
from importlib.metadata import entry_points
def load_exporter(name: str):
matches = list(entry_points(group="myapp.exporters", name=name))
if len(matches) > 1:
raise LookupError(f"duplicate exporter: {name}")
if not matches:
raise LookupError(f"exporter not installed: {name}")
return matches[0].load()
if __name__ == "__main__":
print(load_exporter("csv")().export([[1, "a"], [2, "b"]]))
load_exporter("xml")
追加パッケージをインストールしてから、本体側のload.pyを実行します。
pip install ./myapp_csv
python load.py
1,a
2,b
Traceback (most recent call last):
...
LookupError: exporter not installed: xml
本体のコードはmyapp_csvを一度もimportしていません。追加パッケージをpip installするだけで生成の候補が増えます。entry_points()にgroupやnameを渡して絞り込む書き方はPython 3.10からで、3.9以前のentry_points()は引数を取らずdictを返していました。3.9を対象に含めるなら、PyPIのimportlib_metadataを使います。
書き方の選び方とFactory Methodを使わないほうがよい場面
ここまでの書き方を、解きたい問題から逆引きすると次のようになります。
| やりたいこと | 選ぶ書き方 | 本記事の例 |
|---|---|---|
| 文字列や設定値で生成クラスを選ぶ | ファクトリ関数 | create_notifier("slack") |
| 同じクラスを別の入力形式から作る | classmethod | Invoice.from_csv_row() |
| 共通処理の途中で生成物だけ変える | Factory Method | AlertService.create_notifier() |
| サブクラスを足すだけで候補に加える | __init_subclass__ | Exporter.for_format() |
| 別パッケージから候補を足す | entry_points | load_exporter() |
クラスの引数渡しによるサブクラス削減
継承版のFactory Methodは、生成物を1種類変えるたびにCreatorのサブクラスが1つ増えます。生成するクラスを差し替えたいだけなら、クラスそのものを引数で受け取れば済みます。
from collections.abc import Callable
class AlertService:
def __init__(self, notifier_factory: Callable[[], Notifier]) -> None:
self._notifier_factory = notifier_factory
def alert(self, to: str, message: str) -> str:
return self._notifier_factory().send(to, f"[ALERT] {message}")
print(AlertService(SlackNotifier).alert("ops", "disk 90%"))
print(AlertService(lambda: EmailNotifier()).alert("ops", "disk 90%"))
slack to #ops: [ALERT] disk 90%
email to ops: [ALERT] disk 90%
引数の型は「引数なしで呼ぶとNotifierを返すもの」なので、クラスでもlambdaでも渡せます。テストでは送信しない偽のNotifierクラスを渡すだけで、テスト用のサブクラスは要りません。
筆者の判断基準は、Creatorのサブクラスが生成メソッドしか上書きしていないならこの形に置き換える、というものです。継承版を残す価値があるのは、サブクラスが生成物と一緒にリトライ回数や宛先の整形といった別の振る舞いも変える場合です。そのときは1つのサブクラスに変更点がまとまっているほうが読みやすくなります。
Abstract Factory・Builderとの違い
Abstract Factoryは、関連する複数の部品(ボタンと入力欄をテーマごとにそろえる、など)をまとめて作るオブジェクトを用意するパターンです。Factory Methodが継承によって生成処理を差し替えるのに対し、Abstract Factoryは生成メソッドを複数持つオブジェクトごと差し替えます。Builderは、生成物を段階的に組み立てる手順を分離するパターンで、どのクラスを作るかではなく、どう組み立てるかを扱います。言語をまたいだ全体像はファクトリパターンの解説、BuilderはBuilderパターンの解説を参照してください。
よくある質問
ファクトリ関数とは何ですか?
オブジェクトの生成や取得を担い、そのオブジェクトを返す関数です。Pythonでは、クラスをdictに入れて文字列で引き、呼び出して返す形がよく使われます。サブクラスで生成を差し替える構造は持たないため、GoFのFactory Methodとは別物で、Simple Factoryと呼ばれます。
ファクトリメソッドとは何ですか?
オブジェクトを作る処理を担うメソッドのことで、デザインパターンとしては、生成メソッドを基底クラスで宣言し、どのクラスを作るかをサブクラスに決めさせるFactory Methodパターンを指します。Pythonではclassmethodで書いた代替コンストラクタもファクトリメソッドと呼ばれるので、文脈でどちらの意味かを確かめてください。
Factory MethodとAbstract Factoryの違いは何ですか?
Factory Methodは、サブクラスによって生成処理を差し替えるパターンです。Abstract Factoryは、関連する複数の生成物を作るメソッド群をオブジェクトにまとめ、そのオブジェクトごと差し替えます。
Pythonでstaticmethodのファクトリメソッドは使えますか?
使えます。staticmethodはclsを自動で受け取らないため、本記事のように基底クラス名を直書きした実装では、サブクラスから呼んでも基底クラスのインスタンスが返ります。継承されうるクラスでは、clsを受け取るclassmethodで書くとサブクラスから呼んだときにサブクラスのインスタンスが返ります。
PythonのFactory Methodに抽象基底クラスは必須ですか?
必須ではありません。abc.ABCを使わず、基底クラスのcreate_notifier()でNotImplementedErrorを送出する形でもFactory Methodは書けます。abc.ABCを使う利点は、生成メソッドの実装漏れをインスタンス生成時のTypeErrorで検出できることです。生成物のNotifierのように、継承させずに型だけを示したいなら、Python 3.8で追加されたtyping.Protocolで構造的な型として宣言し、mypyなどの型チェッカーで確かめる方法もあります。