🐕

classmethodを使った代替コンストラクタ

2024/02/25に公開

クラスメソッドを使って長すぎる__init__にお別れを告げる

こんばんはYosematです。
TypeHintとPydanticの狂信者でこれまでにもdataclassを捨ててpydanticに乗り換えるなどでTypeSafeなクラスであるdataclassやpydanticのすばらしさをご紹介しています。
今日はこれらと相性のいいクラスメソッドによる代替コンストラクタについてご説明します。

複雑な初期化ロジックを書きたい

「dataclassやpydanticは__init__を自動で作っちゃうけど、俺はもっと複雑な初期化ロジックを使いたいんだが...」という人もいるでしょう。

いろんな方法で初期化することができる多機能な__init__メソッド

上の例では__init__メソッドは

  1. どの方法で初期化を行うかを判定する
  2. (p, q, r)から(x, y)を作成する
  3. メンバーself.xself.yを定義し値を代入する

の3つの役割を持っています。

しかし多くの場合__init__メソッドを高機能にすることは間違っています。

しかし__init__メソッドには3.の機能だけを持たせておくことがおすすめです。
なぜかといえばインスタンスの初期化方法は1つとは限らないし今後増えるかもしれないからです。__init__関数がたくさんの責任を背負う事自体がよくないことだし、今後新たな初期化方法が増えるたびに__init__メソッドを大改造するのはとても大変です。

初期化ロジックをクラスメソッドに外だしする

もし複雑なロジックで初期化を行いたい場合、初期化ロジックをクラスメソッドに外だししましょう

このようにすることで

  1. どの方法で初期化を行うかを判定する
    という機能は不要になりました。ユーザーは(x, y)から初期化するなら通常通り__init__を呼べば良いし、(p, q, r)から初期化するならfrom_pqrメソッドを呼べばよいのです。

  2. (p, q, r)から(x, y)を作成する
    という機能は__init__メソッドから消え去り、__init__がすっきりしました。今後新しい初期化手法を加えたくなっても新たなクラスメソッドを生やすだけで対応できます。

  3. メンバーself.xself.yを定義し値を代入する
    これは引き続き__init__の役割として残っています。しかしこの機能はdataclassやpydanticを使えば実装する必要すらありません


これがモダンなPythonにおけるクリーンで拡張性の高い静的なクラス定義です。ちなみにこのfrom_pqrのような代替コンストラクタパターンはRustのような静的型付言語では使って当たり前ですしpytorch_lightningのload_from_checkpointなどでもおなじみです。自信を持って使ってください。

Discussion