classmethodを使った代替コンストラクタ
__init__
にお別れを告げる
クラスメソッドを使って長すぎるこんばんはYosematです。
TypeHintとPydanticの狂信者でこれまでにもdataclassを捨ててpydanticに乗り換えるなどでTypeSafeなクラスであるdataclassやpydanticのすばらしさをご紹介しています。
今日はこれらと相性のいいクラスメソッドによる代替コンストラクタについてご説明します。
複雑な初期化ロジックを書きたい
「dataclassやpydanticは__init__
を自動で作っちゃうけど、俺はもっと複雑な初期化ロジックを使いたいんだが...」という人もいるでしょう。
いろんな方法で初期化することができる多機能な__init__
メソッド
上の例では__init__
メソッドは
- どの方法で初期化を行うかを判定する
-
(p, q, r)
から(x, y)
を作成する - メンバー
self.x
とself.y
を定義し値を代入する
の3つの役割を持っています。
しかし多くの場合__init__
メソッドを高機能にすることは間違っています。
しかし__init__
メソッドには3.の機能だけを持たせておくことがおすすめです。
なぜかといえばインスタンスの初期化方法は1つとは限らないし今後増えるかもしれないからです。__init__
関数がたくさんの責任を背負う事自体がよくないことだし、今後新たな初期化方法が増えるたびに__init__
メソッドを大改造するのはとても大変です。
初期化ロジックをクラスメソッドに外だしする
もし複雑なロジックで初期化を行いたい場合、初期化ロジックをクラスメソッドに外だししましょう
このようにすることで
-
どの方法で初期化を行うかを判定する
という機能は不要になりました。ユーザーは(x, y)
から初期化するなら通常通り__init__
を呼べば良いし、(p, q, r)
から初期化するならfrom_pqr
メソッドを呼べばよいのです。 -
(p, q, r)
から(x, y)
を作成する
という機能は__init__
メソッドから消え去り、__init__
がすっきりしました。今後新しい初期化手法を加えたくなっても新たなクラスメソッドを生やすだけで対応できます。 -
メンバー
self.x
とself.y
を定義し値を代入する
これは引き続き__init__
の役割として残っています。しかしこの機能はdataclassやpydanticを使えば実装する必要すらありません。
これがモダンなPythonにおけるクリーンで拡張性の高い静的なクラス定義です。ちなみにこのfrom_pqr
のような代替コンストラクタパターンはRustのような静的型付言語では使って当たり前ですしpytorch_lightningのload_from_checkpointなどでもおなじみです。自信を持って使ってください。
Discussion