Pythonで関数と演算子をオーバーロードする

オーバーロードとは?

オーバーロードとは、プログラミングの文脈では、関数や演算子が関数に渡されるパラメータや演算子が作用するオペランドによって異なる振る舞いをする能力のことを指します。

今回は、Pythonで関数のオーバーロードと演算子のオーバーロードをどのように行うかを見ていきます。

メソッドをオーバーロードすることで、再利用性が促進されます。

例えば、わずかな違いのある複数のメソッドを書く代わりに、1つのメソッドを書いてそれをオーバーロードすることができます。

また、オーバーロードはコードの明快さを向上させ、複雑さを解消します。

オーバーロードは非常に便利な概念です。

しかし、それに付随するデメリットもいくつかあります。

オーバーロードは、継承の境界を越えて使用すると、混乱を招くことがあります。

過剰に使用すると、オーバーロードされた関数を管理するのが面倒になる。

この記事の残りの部分では、関数と演算子のオーバーロードについて詳しく説明します。

Pythonにおける関数のオーバーロード

関数がどのように定義されているかによって、0個、1個、2個、あるいは多くのパラメータを付けて呼び出すことができます。

これは「関数のオーバーロード」と呼ばれます。

関数のオーバーロードはさらに、組み込み関数のオーバーロードとカスタム関数のオーバーロードの2種類に分けられます。

次のセクションでは、この2つのタイプについて見ていきます。

組み込み関数のオーバーロード

Python の組み込み関数のデフォルトの振る舞いを変更することができます。

対応する特別なメソッドをクラスで定義するだけです。

ここでは、Pythonのlen()`関数を使って、Purchaseクラスで試してみましょう。

class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer


def __len__(self):
        return len(self.basket)


purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))


出力

3


len()関数の振る舞いを変更するために、クラス内にlen()という特別なメソッドを定義しました。

このクラスのオブジェクトをlen()に渡すと、その結果はカスタム定義した関数、つまりlen()` を呼び出して得られます。

この出力は、len() を使ってバスケットの長さを取得できていることを示しています。

もし、__len__()関数がオーバーロードされていないオブジェクトに対して len() を呼び出すと、以下のように TypeError が発生します。

class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer


purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))


出力されます。

Traceback (most recent call last):
  File "C:/Users/admin/func.py", line 8, in <module
    print(len(purchase))
TypeError: object of type 'Purchase' has no len()


Note: Python は len() 関数が整数を返すことを想定しているので、関数をオーバーロードする際にはこの点を考慮しなければなりません。

もしオーバーロードした関数が整数以外のものを返すと予想される場合、TypeErrorが発生します。

上の例の len() メソッドの振る舞いを、その実装である __len__() の定義内で変更することができます。

バスケットの長さを返す代わりに、何か別のものを返すようにしてみましょう。

class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer


def __len__(self):
        return 10;


purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))


出力します。

10


かごの長さを返す代わりに、今度は指定した値を返します。

ユーザー定義関数のオーバーロード

Pythonでユーザー定義関数をオーバーロードするには、渡されたパラメータに応じて関数内で異なるコードが実行されるように、関数ロジックを記述する必要があります。

次の例を見てください。

class Student:
    def hello(self, name=None):
        if name is not None:
            print('Hey ' + name)
        else:
            print('Hey ')


# Creating a class instance
std = Student()


# Call the method
std.hello()


# Call the method and pass a parameter
std.hello('Nicholas')


出力

Hey
Hey Nicholas


クラス Student を作成し、hello() という名前の関数を一つ作成しました。

このクラスは name というパラメータを受け取りますが、これは None に設定されています。

つまり、このメソッドはパラメータがあってもなくても呼び出すことができます。

このクラスのインスタンスを作成して、この関数を 2 回呼び出すことにしました。

関数を呼び出す方法が2つあるため、関数のオーバーロードを実装しています。

さて、関数のオーバーロードの仕組みがわかったところで、次のセクションでは演算子のオーバーロードに焦点を当てます。

演算子のオーバーロード

Python では、使用するオペランドに応じて演算子のデフォルトの動作を変更することができます。

これは「演算子のオーバーロード」と呼ばれます。

Pythonの演算子の機能は、組み込みクラスに依存します。

しかし、同じ演算子でも、異なる型に適用された場合は動作が異なります。

良い例が「+」演算子です。

この演算子は、2つの数値に適用すると算術演算を行い、2つの文字列を連結し、2つのリストを結合します。

演算子のオーバーロードの例

Pythonの演算子オーバーロードの動作を見るには、Pythonターミナルを起動し、以下のコマンドを実行します。

&gt;&gt;&gt; 4 + 4
8
&gt;&gt;&gt; "Py" + "thon"
'Python'


最初のコマンドでは、「+」演算子を使用して2つの数値を加算しています。

2番目のコマンドでは、同じ演算子を使って2つの文字列を連結しています。

この場合、「+」演算子には2つの解釈がある。

数字の足し算に使われる場合は、「加算演算子」と呼ばれます。

文字列の足し算に使われる場合は「連結演算子」と呼ばれます。

つまり、”+” 演算子は int クラスと str クラスに対してオーバーロードされたものであると言える。

演算子のオーバーロードを実現するためには、クラス定義の中で特別なメソッドを定義します。

メソッド名はダブルアンダーコア( _ )で始まり、ダブルアンダーコアで終わります。

演算子のオーバーロードは __add__() という特別なメソッドで行います。

このメソッドは intstr の両方のクラスで実装されています。

次のような式を考えてみましょう。

x + y


Python はこの式を x.__add__(y) として解釈します。

呼び出される __add__() のバージョンは、 xy の型に依存します。

&gt;&gt;&gt; x, y = 5, 7


&gt;&gt;&gt; x + y


12
&gt;&gt;&gt; x.__add__(y)
12
&gt;&gt;&gt;


上記の例は、+ 演算子とその特殊メソッドの使い方を示しています。

次の例は、Python でさまざまな演算子をオーバーロードする方法を示しています。

import math


class Point:


def __init__(self, xCoord=0, yCoord=0):
        self.__xCoord = xCoord
        self.__yCoord = yCoord


# get x coordinate
    def get_xCoord(self):
        return self.__xCoord


# set x coordinate
    def set_xCoord(self, xCoord):
        self.__xCoord = xCoord


# get y coordinate
    def get_yCoord(self):
        return self.__yCoord


# set y coordinate
    def set_yCoord(self, yCoord):
        self.__yCoord = yCoord


# get current position
    def get_position(self):
        return self.__xCoord, self.__yCoord


# change x &amp; y coordinates by p &amp; q
    def move(self, p, q):
        self.__xCoord += p
        self.__yCoord += q


# overload + operator
    def __add__(self, point_ov):
        return Point(self.__xCoord + point_ov.__xCoord, self.__yCoord + point_ov.__yCoord)


# overload - operator
    def __sub__(self, point_ov):
        return Point(self.__xCoord - point_ov.__xCoord, self.__yCoord - point_ov.__yCoord)


# overload &lt; (less than) operator
    def __lt__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) &lt; math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)


# overload &gt; (greater than) operator
    def __gt__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) &gt; math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)


# overload &lt;= (less than or equal to) operator
    def __le__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) &lt;= math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)


# overload &gt;= (greater than or equal to) operator
    def __ge__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) &gt;= math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)


# overload == (equal to) operator
    def __eq__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) == math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)


point1 = Point(2, 4)
point2 = Point(12, 8)


print("point1 &lt; point2:", point1 &lt; point2)
print("point1 &gt; point2:", point1 &gt; point2)
print("point1 &lt;= point2:", point1 &lt;= point2)
print("point1 &gt;= point2:", point1 &gt;= point2)
print("point1 == point2:", point1 == point2)


出力

出力:“`
point1 < point2: True
point1 point2: False
point1 <= point2: True
point1 = point2: False
point1 == point2: False


Point クラスには、直交座標系である `xCoord` と `yCoord` という名前の `__xCoord` と `__yCoord` という 2 つのプライベート属性があります。これらの属性に対して、セッターとゲッターのメソッドを定義しています。get_position()` メソッドは現在の位置を取得するのに役立ち、`move()` メソッドは座標を変更するのに役立ちます。

コードから抜粋した次の行を考えてみましょう。

def __add__(self, point_ov):

この行は、このクラスで + 演算子をオーバーロードするのに役立ちます。__add__()` メソッドは、1 つの Point オブジェクトの個々の座標を別の Point オブジェクトに追加することで、新しい Point オブジェクトを作成します。最後に、新しく作成されたオブジェクトを呼び出し元に返します。これは、次のような式を書くのに役立ちます。

point3 = point1 + point2

“`

Python は上記の式を point3 = point1.__add__(point2) として解釈します。

そして、__add__() メソッドを呼び出して 2 つの Point オブジェクトを追加します。

その結果は “point3” に代入されます。

なお、 __add__() メソッドを呼び出すと、 point1 の値は self パラメータに、 point2 の値は point_ov パラメータに代入されることに注意してください。

他の特殊なメソッドもすべて同じように動作します。

オーバーロードする演算子

次の表は、より一般的にオーバーロードされる数学演算子と、オーバーロードするクラスメソッドを示したものです。

| 演算子|メソッド|…

| +__add__(self, other)__add__(self, other)__add__(self, other)| `add(self)

| * | __mul__(self, other) | __mul__(self, other) | __mul__(self, other)
|
/truediv(self, other)| |mul(self, other)|mul(self, other)
| %__mod__(self, other) | | &lt; | __mod__(self, other)
|
<|lt(self, other)|<=|le(self, other)|le(self, other)|le(self, other)
| == | __eq__(self, other) | __eq__(self, other) | __eq__(self, other) | !
|
!=|ne(self, other)| |!=|ne(self, other)

| &gt;= | __ge__(self, other) | | | &gt;= | | `ge(self, other)

結論

Pythonは関数と演算子のオーバーローディングの両方をサポートしています。

関数のオーバーロードでは、多くのPython関数に同じ名前を使用することができますが、パラメータの数や種類が異なります。

演算子のオーバーロードでは、クラスのスコープ内でPython演算子の意味を変更することができます。

</module

タイトルとURLをコピーしました