50199


多くのプログラミング言語では、オブジェクトに対する演算子の振る舞いを再定義できます。  
例えばpythonでは、オブジェクトに`__eq__()`等の特殊メソッドで定義できます。

これは一般的にはオーバーロードと呼ばれますが、なぜオーバーライドではなくオーバーロードと呼ぶのでしょうか……？

オーバーロードは同オブジェクトに複数の同名メソッドを定義することだと思いますが、演算子の再定義の場合、感覚的には、javaで言うtoString()のオーバーライドのようなイメージを持っているので、「オーバーライド」がしっくりくる気がします。


CC BY-SA 4.0
2018-11-11T03:50:58.900
50196
2022-01-30T04:23:38.227
30937
question
python プログラミング言語
演算子の「オーバーロード」？「オーバーライド」？


> オーバーロードは同オブジェクトに複数の同名メソッドを定義すること

おっしゃるとおりだと思いますが、（それ以前からあったかも知れませんが）もともとはC++等で、`String` \+ `int`や`String` \+
`自作クラス`など、言語が演算子を定義していない型同士の演算に演算子を適用するために作られたものだと認識しています。  
つまり、あるオブジェクトに、引数の型に応じた演算子を複数定義するための機能だったので、オーバーロードなのだとおもいます。

Pythonの場合は変数は型を持たないので、引数の型に応じた演算子を追加ではなく、既存の演算子の上書きに見えてしまうので違和感を感じておられるのだと思いますが、先に有名になってしまった型に応じた演算子の追加としてのオペレーターオーバーロードが有名になっているので、演算子の定義（追加も上書きも）広くオペレーターオーバーロードと呼ばれているのだと思います。


CC BY-SA 4.0
2018-11-11T04:15:50.733
50197
2018-11-11T04:15:50.733
14745
50196
answer


# 簡単なお返事

本質的にシグネチャが違うメソッドを定義するからです。

# 詳細なお返事

## シグネチャ

メソッドの

  * メソッド名
  * 引数の量とその型の登場する順序
  * 戻り値の型

をあわせた情報を **シグネチャ** といいます。  
メソッドのオーバライドとオーバロードはそれぞれ

  * **オーバライド** : 親クラスの **同一シグネチャなメソッド** の実装を変更する形でメソッドを定義するもの
  * **オーバロード** : 新しく **メソッド名は同一だが、異なるシグネチャのメソッド** を定義するもの

という形で理解する事ができる言葉です。  
ただし理由は後述しますがシグネチャには暗黙の第一引数である `self` (自クラスの型) は含まれません。

## シグネチャの意義

シグネチャが変わってしまうと、利用者サイドコードでは  
そのメソッドに対する使い方(呼び出し方と、戻り値の使い方)が変わってしまいます。

そこでシグネチャを変更するか？変更しないか？という視点で、  
メソッドの実装を新たに作り変えるための文法を区別するために、  
オーバライドとオーバロードという用語は区別されています。

`self` の型をシグネチャには含まないのも、  
`self`の型が違うことを言語側で吸収可能であるという事情からです。

## pythonでの例

しかしpythonは型が _ありません。_ (正確には型が _動的_ です。)

さて、ここで演算子を新しく定義する時のことを考えます。  
pythonのような動的型付け言語では意識し辛いことなのですが  
実は **引数と戻り値の型** が、親クラスとは異なっており、  
本質的に親クラスとは別物になっています。

このことは、たとえば以下のようなコードを書いてみると理解できるかもしれません。

[code]

    class Parent():
      def __add__(self, other):
        # 本来はこの辺で足し算処理
        # この時点で子クラスに何がいるか想定はできないため
        # (メタプログラミングしない限り)ここではParentを返さざるを得ない
        return Parent()
    
    class Child(Parent):
      def fuga(self):
        print("Child::fuga called")
    
    lhs = Child()
    rhs = Child()
    
    result = (lhs + rhs)
    # 以下の処理は、Childを足したのだから当然呼び出せてほしい。
    # ここで期待しているのは、親クラスの１例(インスタンス)としてのChildインスタンス"ではない"。
    # つまり本質的に受け取った側は、子クラスにしかない処理を叩きたいのだから
    # resultにはParent型ではなく、具体的なChild型を期待している事がわかる。
    result.fuga() # しかし！このメソッドはParentに存在しないため、エラー
    
[/code]

もちろん親クラスで定義された `__add__()` によって子クラスを足すこと自体はできます。  
しかしそれで帰ってくる値の型は、親クラスのものになってしまっています。

つまり _本質的には、_ ここには Childという型で値を返す _規約_ が利用者コード側との間にあって、  
Parentの型を返す _規約_ で十分だった 親クラスとは **シグネチャが異なっています。**

したがってオーバライドではなく、オーバロードになっています。

また `other` として受け取りたいのは、基本的に子クラスです。(=シグネチャが異なります)  
なぜなら `other` において、親クラス側だけの変数では十分でなく  
子クラスとしての変数群を触りたくなる実装と考えられるからです。  
大抵の場合、足し算の右辺に親クラス変数が来たら処理できないでしょう。  
変数型は、 **継承ツリーと関係なく** ある都合の良い型になっている必要があります。

このように演算子オーバロードを行うときには、利用者コードで期待するであろう型を  
継承ツリーによらず作り出す必要があります。

これは `toString()` の場合とはちょっと違った事情です。  
`toString()` であれば戻り値の型も、引数の型も特に違いありません。そのため影響がありません。

ちなみにこのような事情から、静的型付け言語を扱うプログラマ達の間では  
(正当性はともかく)標語的に **型を記載しなくても型はそこにある** と言われることがあります。

## 動的言語における対処

pythonにおけるこの問題に対する対処としては、メタプログラミングを使って

[code]

    class Parent():    
      def __add__(self, other):
        # selfのクラスをインスタンス化することで値を返す。
        # これにより戻り値の型が状況により親クラスになったり子クラスになったりする！
        return self.__class__()
    
[/code]

というような回避をするのが慣例です。

しかしこの場合、実は状況に応じて帰ってくる型が、いつの間にか変わっていると言うことです。  
ということはオーバライドではなく、オーバロードをメタプログラミングで実装していることになります。  
(対偶で言えば、メタプログラミングが必要になってしまうのはそのような事情です)

## まとめ

  * 演算子オーバロードを行う場合、本質的に引数や戻り値型が、継承前と異なる型になります
  * これは演算子のメソッドシグネチャが異なることを意味します。
  * そのため演算子オーバライドではなく、演算子オーバロードと呼ぶと考えられます。


CC BY-SA 4.0
2018-11-11T04:40:52.797
50199
2018-11-11T05:26:36.420
2018-11-11T05:26:36.420
30827
30827
50196
answer


本来は「利用者定義演算子」と呼ぶものになります。

[利用者定義演算子 -
Wikipedia](https://ja.wikipedia.org/wiki/%E5%88%A9%E7%94%A8%E8%80%85%E5%AE%9A%E7%BE%A9%E6%BC%94%E7%AE%97%E5%AD%90)

多重定義(Overload)と呼ぶ人がいるのは、C++が利用者定義演算子を多重定義の一部としていたためでしょう。


CC BY-SA 4.0
2022-01-29T20:05:04.787
86007
2022-01-30T04:23:38.227
2022-01-30T04:23:38.227
3060
51150
50196
answer