プログラミング
オーバーロードのオーバーロード
Overloaded Overloading (powerfulpython.com)
要約
Pythonでは、メソッドのオーバーロードはサポートされていませんが、演算子のオーバーロードはサポートされています。これは、`+`演算子が数値の加算と文字列の連結の両方に使用できる理由を説明しています。メソッドのオーバーロードが必要な場合は、ディスパッチングメソッドや`@singledispatch`デコレータを使用して自分で実装する必要があります。
全文翻訳
オーバーロードのオーバーロード
ニュースレター: "Pythonはオーバーロードをサポートしていませんが、`+`は加算または連結のいずれかとして機能しますが、これは正しいですか?" 私の同僚のPeterがかつて私に尋ねました。この質問を説明してから、それに答えましょう。そして、それは "overloading" という言葉から始まります。これはPythonでは2つの異なる意味を持っています。
Pythonでクラスを書くとき、各メソッドは1つのバージョンしか持つことができません。例えば、これはうまくいきません:
import math
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def distance(self, other_point):
# この点から別の点までの距離
return math.sqrt( (self.x-other_point.x)**2 + (self.y-other_point.y)**2 )
def distance(self, x, y):
# 他のx-y座標までの距離
return math.sqrt( (self.x-x)**2 + (self.y-y)**2 )
2つのdistance()メソッドが見えますか?定義が2回されているのがわかりますか?Pythonでは、2番目のバージョンのみが保持されます。最初のバージョンはサイレントに上書きされ、削除されます。もしあなたが1つの引数(最初のdistance()には一致しますが、2番目には一致しません)でPoint.distance()を呼び出すと、エラーが発生します。
しかし、このクラスをJavaに翻訳すると、状況は異なります。Javaでは、distance()の両方のバージョンを定義できます。そしてJavaは、渡された引数の数に応じて、どちらか一方を使用します。これは "メソッドオーバーロード" と呼ばれます。しかし、Pythonではそれができません。
Peterはこのことを知っていたので、彼は`+`演算子について疑問に思いました。そしてなぜPythonはそれを異なる操作に使用させるのでしょうか?答えは、Pythonはメソッドオーバーロードをサポートしていませんが...演算子オーバーロードはサポートしているということです。そして、それがあなたが`+`を文字列と数値の両方に使用でき、実際には "マジックメソッド" のフックを使用して、あなた自身のクラスでも機能させることができる理由です。
`+`演算子の場合、`__add__`という名前のメソッドを定義することでそれを行います。例えば、Pointクラスで:
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
# ...
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
この`__add__`メソッドは、任意のクラスで定義できます。定義すると、それらのインスタンスを一緒に加算できるようになります:
>>> p = Point(1,1) + Point(2,2)
>>> print(p.x, p.y)
3 3
確認すると、float()とstr()の両方に独自の`__add__`メソッドがあることがわかります。そして、それぞれ加算と文字列連結を実装しています。これがどのように機能するか見てください。
プログラミング言語について話すとき、"オーバーロード" は2つの異なることを指します。そしてPythonはそれらのうちの1つをサポートしますが、もう1つはサポートしません。("overloaded"という言葉自体が...オーバーロードされていると言うかもしれません。)
P.S. Pointクラスに戻りますが、もし本当にdistance()メソッドのオーバーロードが必要な場合はどうしますか?答え: それを行うには2つの方法があります。より一般的な方法は、distance()をディスパッチングメソッドにすることによって、自分でメソッドオーバーロードを実装することです。つまり、その仕事は直接計算を行うことではなく、どのメソッドがそれを処理すべきかを決定し、それを呼び出すことです。通常、ジェネリックな引数名を使用し、`isinstance()`または特定の引数の有無をチェックしてこれを行います:
import math
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def distance(self, first, second=None):
if isinstance(first, Point):
# 他のPointへの距離
assert second is None
return self.distance_to_other(first)
elif isinstance(first, tuple):
# (x, y)タプルへの距離
assert second is None
return self.distance_to_pair(first)
else:
# 座標への距離
assert second is not None
return self.distance_to_coordinates(first, second)
def distance_to_coordinates(self, x, y):
'この点から他のx-y座標までの距離'
return math.sqrt( (self.x-x)**2 + (self.y-y)**2 )
def distance_to_other(self, other_point):
'この点から別の点までの距離'
return self.distance_to_coordinates( other_point.x, other_point.y)
def distance_to_pair(self, pair):
'この点から(x, y)タプルまでの距離'
x, y = pair
return self.distance_to_coordinates(x, y)
2番目の、そして一般的に良い方法は、Pythonのfunctoolsモジュールにある`@singledispatch`デコレータを使用することです。これにより、最初の引数の型に基づいて異なる "ディスパッチ" メソッドをきれいに登録できます。ただし、この例のPointクラスでは、ディスパッチロジックが複雑すぎるため機能しません。しかし、もしあなたがこのようなことをする必要があるなら、まず`@singledispatch`を使ってみてください。
ニュースレター: チーム向けニュースレター研修