HOWTO · Python
Python でのメソッドのオーバーロード
このチュートリアルでは、Python でのメソッドのオーバーロードとその利点を例とともに紹介します。
このページの内容
Pythonは、同じ名前を持つ複数の定義から、引数の数や型に合うメソッドや関数を選ぶことはありません。後から定義したものが、先に定義した名前の束縛を置き換えます。異なる呼び出し方に対応するには、既定値付きの引数を使う、入力型を明示的に判定して分岐する、またはサードパーティの多重ディスパッチパッケージを使う方法があります。これらは異なる手法で、登録済みの実装から引数の型に応じて選ぶのは最後の方法だけです。
Pythonはメソッドのオーバーロードをサポートするか
Pythonには、シグネチャに基づくメソッドのオーバーロードが標準ではありません。クラス内で同じ名前のメソッドを複数回定義すると、その名前には最後の定義だけが残ります。これは、クラスで __add__ などの特殊メソッドを定義して演算子の動作をカスタマイズする演算子オーバーロードとは異なります。また、任意の引数を受け取る単一のメソッドとも異なります。後者は実装を1つだけ持ち、受け取った値に応じて処理を決めます。
たとえば、挨拶するメソッドでは、名前が渡されたらそれを使い、省略されたら既定の値を使えます。これは任意引数の動作であり、2つのオーバーロードではありません。
任意の引数には既定値を使う
入力が本当に任意で、どちらの呼び出し方でも同じ処理を行える場合は、既定値を使います。次の例では、メソッドを1つ定義し、名前を渡す場合と省略する場合の両方で呼び出します。
次の例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
class Greeter:
def greet(self, name="there"):
print(f"Hello, {name}!")
Greeter().greet()
Greeter().greet("Ada")
提示された実行では、次の呼び出しが実行され、次の内容が出力されました。表示される文字列は、コードブロックを正確に維持しているため英語のままです。
Hello, there!
Hello, Ada!
呼び出し側が name を省略すると、既定の引数として "there" が使われます。名前を渡した場合は、その値が使われます。リストや辞書などの変更可能な値を既定値にする場合は、None を使ってメソッド内で新しい値を作成してください。そうすれば、複数回の呼び出しで同じ変更可能なオブジェクトを共有せずに済みます。
動作を変えるには型をチェックする
少数の入力型に対して異なる動作をさせたい場合は、1つの関数で入力を調べ、明示的に分岐できます。次の関数が受け付けるのは、2つの整数または2つの文字列です。それ以外の組み合わせでは、Pythonの + 演算子の偶然の動作に頼らず、明確な TypeError を発生させます。
このセクションのコードと表示されるラベルや出力は英語のままです。
def combine(left, right):
if type(left) is int and type(right) is int:
return left + right
if type(left) is str and type(right) is str:
return left + right
raise TypeError("combine expects two integers or two strings")
print(combine(7, 11))
print(combine("Hello, ", "Python"))
提示された実行では、この例が実行され、次の内容が出力されました。コードブロックをそのまま維持しているため、表示される文字列は英語のままです。
18
Hello, Python
これは1つの関数内での明示的な型チェックであり、自動的なメソッドのオーバーロードではありません。type(...) is ... による厳密なチェックでは、int のサブクラスである bool なども対象外になります。サブクラスも受け付ける場合は、代わりに isinstance を使ってください。この関数は、整数と文字列のような異なる型の組み合わせも意図的に受け付けません。アプリケーションで意味のある組み合わせに限って、分岐を追加してください。
型に応じた選択には多重ディスパッチを使う
複数の引数型に基づいて処理を選ぶには、サードパーティの multipledispatch パッケージを使って、シグネチャごとに実装を登録できます。プログラムを実行する環境に pip install multipledispatch でインストールしてください。次の例で定義するのはクラスメソッドではなく関数です。3つの整数を受け取るシグネチャと、3つの浮動小数点数を受け取るシグネチャをそれぞれ登録します。
このセクションのコードと表示されるラベルや出力は英語のままです。
try:
from multipledispatch import dispatch
except ImportError:
print("Install multipledispatch to run this example.")
else:
@dispatch(int, int, int)
def multiply(first, second, third):
return first * second * third
@dispatch(float, float, float)
def multiply(first, second, third):
return first * second * third
print(multiply(3, 7, 8))
print(multiply(3.6, 5.9, 9.9))
提示された実行では、インストールメッセージのフォールバック処理が実行され、次の内容が出力されました。コードブロックをそのまま維持しているため、表示されるメッセージは英語のままです。
Install multipledispatch to run this example.
依存パッケージをインストールした状態で、登録したディスパッチ関数の呼び出しが成功するかどうかは実行確認されていません。パッケージがインストールされていれば、登録済みのシグネチャに一致する呼び出しでは、対応する関数の実装が選ばれます。ここで登録されているのは例に示したシグネチャだけです。整数と浮動小数点数が混在する引数には、一致する登録がありません。プログラムでサポートする組み合わせのシグネチャを追加し、この依存パッケージがプロジェクトに適しているかも検討してください。
呼び出しシグネチャに応じて手法を選ぶ
呼び出し側が入力を省略でき、1つの実装で省略時と指定時の両方を処理できるなら、既定の引数を選びます。意図する動作が少数で、それらを1つの関数内にまとめるとルールを読み取りやすい場合は、明示的な型チェックを使います。引数型の組み合わせによる選択が設計の中心にあり、追加の依存パッケージを許容できるなら、多重ディスパッチを使います。
どの方法を選んでも、プログラムが自動的に高速化したり簡潔になったりするわけではありません。サポートする呼び出し方を明確にしてください。既定値を文書化し、受け付ける型の各組み合わせに対する動作を定義し、多重ディスパッチでは必要なシグネチャをすべて登録します。どの方法を選んでも、Pythonの通常の名前束縛の仕組みは変わりません。