HOWTO · Python

Python でコンストラクターをオーバーロードする

このチュートリアルでは、Python でコンストラクターをオーバーロードする方法を示します

このページの内容

Pythonでは、引数に応じて複数の__init__メソッドから実行するものを選ぶことはありません。クラス内で__init__を複数回定義すると、後の定義が前の定義を置き換えるため、インスタンス作成時に使えるのは最後の定義だけです。複数の方法でオブジェクトを初期化するには、1つの初期化メソッド内で分岐するか、名前付きファクトリメソッドを用意するか、初期化パラメーターに適切なデフォルト値を設定します。

Pythonにおけるコンストラクターのオーバーロードとは

シグネチャに基づくコンストラクターのオーバーロードがある言語では、実行時に引数の型や数に応じてコンストラクターを選べます。Pythonでは、__init__を繰り返し定義しても、そのようには動作しません。1つの__init__で柔軟な引数を受け取り、意図的に処理を分けることはできますが、これは1つのメソッド内での振り分けであり、言語標準のオーバーロードではありません。呼び出し側がクラスで受け付ける値を把握できるよう、対応する形式を明示しましょう。

*argsで引数の型や数に応じて処理を分ける

*argsパラメーターを使うと、位置引数をタプルとして受け取れます。その長さや値の型を調べ、インスタンスを適切に初期化できます。値を読み取る前に引数が存在することを確認し、未対応の入力形式を受け取った場合の動作も決めておきましょう。次の実行可能な例では、整数または文字列を1つ受け取り、選択された値を表示します。

この例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

class Greeting:
    def __init__(self, *args):
        if len(args) != 1:
            self.message = "Pass one integer or string"
        else:
            value = args[0]
            if isinstance(value, int):
                self.message = str(value)
            elif isinstance(value, str):
                self.message = "Hello from " + value
            else:
                self.message = "Pass an integer or string"


print(Greeting("Delft").message)

args[0]にアクセスする前に引数の数を確認するため、位置引数がない呼び出しでも安全に処理できます。この数のチェックは、余分な引数も拒否します。Pythonではboolはintのサブクラスなので、isinstance(True, int)はTrueになります。ブール値を整数として受け付けない場合は、型が完全に一致するかを確認してください。

位置引数の数ごとに明確な意味がある場合は、引数の数に基づく分岐が役立ちます。この初期化メソッドが受け付けるのは、引数が0個、1個、または2個の場合です。引数なしならデフォルトの範囲を使い、1個なら終了値に使い、2個なら開始値と終了値に使います。それ以外の数の場合、例は英語で「Pass zero, one, or two values」と表示します。各分岐ではargsから値を読む前に引数の数を確認するため、引数なしや未対応の数の場合に、存在しない値を参照することはありません。範囲の説明も英語で「Range from 0 to 10」のように表示されます。

この例を実行します。

class RangeSpec:
    def __init__(self, *args):
        if len(args) == 0:
            self.start, self.stop = 0, 10
        elif len(args) == 1:
            self.start, self.stop = 0, args[0]
        elif len(args) == 2:
            self.start, self.stop = args
        else:
            self.start, self.stop = None, None
            self.message = "Pass zero, one, or two values"

    def describe(self):
        if self.start is None:
            return self.message
        return f"Range from {self.start} to {self.stop}"


for spec in (RangeSpec(), RangeSpec(5), RangeSpec(2, 5), RangeSpec(1, 2, 3)):
    print(spec.describe())

4つの呼び出しで、対応するすべての引数の数と、対応範囲を超える数を1つずつ確認します。対応する形式が増える場合は、呼び出し側が位置引数の数ごとの意味を覚えなくて済むよう、名前付きパラメーターやファクトリメソッドを使うとよいでしょう。

@classmethodファクトリメソッドを代替コンストラクターとして使う

@classmethodは、慣例的にclsと名付けるクラスを最初の引数として受け取ります。ファクトリメソッドでは、別の表現形式から通常の初期化メソッドに必要な値へ変換し、cls(...)を返せます。これによりインスタンスを作成できます。また、clsはメソッドを呼び出したクラスを指すため、サブクラスでも自然に使えます。ファクトリメソッドは名前付きの生成方法であり、__init__にオーバーロードを追加するものではありません。

この例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def from_pair(cls, pair):
        return cls(pair[0], pair[1])


point = Point.from_pair((3, 4))
print(point.x, point.y)

入力形式に意味のある名前を付けられる場合は、from_file、from_row、from_coordinatesのように、別々のファクトリメソッドを用意しましょう。変換の一部として妥当性確認や正規化が必要なら、その処理をファクトリメソッドに含めます。オブジェクトに必要な状態を確立する場所は、通常の初期化メソッドに一本化してください。

初期化の任意項目にはデフォルト引数を使う

パラメーターが本当に省略可能で、その意味が明確な場合は、デフォルト値が適しています。分岐処理を増やさずに、呼び出し側が値を省略できるためです。数値、文字列、Noneなどの不変値をデフォルトに使いましょう。リストなどの可変値は、インスタンス間で誤って共有されないよう、通常はメソッド内で作成します。

この例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

class Profile:
    def __init__(self, name="Guest", score=0):
        self.name = name
        self.score = score


for profile in (Profile(), Profile("Mina"), Profile("Mina", 7)):
    print(profile.name, profile.score)

この例では、呼び出し側は任意の値をどちらも指定しない、一方の名前だけを指定する、または両方の値を指定する、という使い方ができます。省略によって複数の異なる意味が生じる場合、パラメーター同士が依存する場合、あるいは位置引数の組み合わせが増えて覚えにくくなる場合は、デフォルト値だけで対応するのは適しません。そのような場合は、キーワード専用パラメーターや名前付きファクトリメソッドを使うと、呼び出しが分かりやすくなります。

適切なコンストラクターパターンを選ぶ

一般的な任意の値が少数なら、デフォルト値を使います。別の表現形式からオブジェクトを作る場合や、生成方法に説明的な名前を付けたい場合は、ファクトリメソッドが適しています。意図するAPIに簡潔で安定した位置引数の形式が含まれる場合に限り、*argsによる振り分けを使い、値を使う前に引数の数と型を確認してください。複雑な妥当性確認では、巧妙な型判定より、明示的な分岐と分かりやすいエラーメッセージのほうが保守しやすくなります。

これらの方法は組み合わせることもできます。ファクトリメソッドで値を検証してから同じ初期化メソッドを呼び出し、任意のフィールドにはデフォルト値を使えます。有効な呼び出しが明確で、未対応の呼び出しに予測可能な動作をする、最もシンプルなインターフェイスを選びましょう。重要なのは、各クラスで有効な__init__の定義は1つだけだという点です。柔軟性は、そのメソッド内の処理や、周囲に用意する名前付きの代替手段で実現します。