2014/06/10

Python Tips:自作クラスの演算子のふるまいを定義したい

Pythonで、自分で作ったクラスの演算子のふるまいを定義する方法をご紹介します。

Pythonでは、「特殊メソッド」(special methods/magic methods)と呼ばれるものを使って +-*/ << += などの各種演算子の挙動を定義することができます。

特殊メソッドというのは、「ふるまいは自分で定義できるけれど、その呼び出しタイミングなど使われ方はあらかじめ決められているメソッド」のこと。ことばでの説明を聞くとなんやらよくわかりませんが、具体例を見るとわかりやすいと思います。たとえば、インスタンスを生成したときに呼び出される __init__ メソッドなんかも特殊メソッドです。 __init__ の場合なんかは顕著に、「ふるまいは自分で決められるけれど、呼び出されるタイミングはあらかじめ決まっているメソッド」ですね。

以下、この特殊メソッドを使って演算子のふるまいを定義する方法を具体的に見ていきます。ここでは典型的なものとして次の4つを見ていきたいと思います。
  • __add__
  • __mul__
  • __lshift__
  • __iadd__


__add__

__add__ は + 演算子に対応した特殊メソッドです。 __add__ の中で定義した処理が + 演算子の処理となります。
class Array(object):
    def __init__(self, values):
        """インスタンス生成時に呼び出されるメソッド
           values には数字を格納したリストを渡す
        """
        self.values = values

    def __add__(self, other):
        """+ 演算子を定義するメソッド
           values 内の要素同士を足し合わせた新しい Array インスタンスを返す """
        return Array(list(map(lambda x, y: x + y,
                          self.values,
                          other.values)))

m1 = Array([3, 5])
m2 = Array([10, 11])

m3 = m1 + m2
print(m3.values)
# => [13, 16]
# __add__ メソッドで定義したとおりの結果となっている

このコードでは、 __add__ メソッドの中に values の要素同士を足し合わせて新たなインスタンスを生成する処理を書いています。 m3 の中身を見ると、 + の挙動が __add__ で定めたとおりになっていることが確認できます。


__mul__

__add__ を詳しく見たので、ここからは手短に行ければと思います。 __mul__ は * 演算子に対応したメソッドです。 multiply multiplication の略ですね。
class Array(object):
    # ここに __init__ などの定義
    # ...

    def __mul__(self, other):
        """* 演算子を定義するメソッド
           values 内の要素同士をかけ合わせた新しい Array インスタンスを返す """
        return Array(list(map(lambda x, y: x * y,
                          self.values,
                          other.values)))

m1 = Array([3, 5])
m2 = Array([10, 11])

m4 = m1 * m2
print(m4.values)
# => [30, 55]
# __mul__ メソッドで定義したとおりの結果となっている

こちらも __add__ と同様の結果が得られていることがわかります。


__lshift__

つづいて、 __lshift__ は << 演算子に対応したメソッドです。
class Array(object):
    # ここに __init__ などの定義
    # ...

    def __lshift__(self, new_element):
        """<< 演算子を定義するメソッド
           新しいオブジェクトは生成せず values の末尾に要素を append する(破壊的メソッド) """
        self.values.append(new_element)
        return self

m1 = Array([3, 5])

m1 << 10
print(m1.values)
# => [3, 5, 10]
# こちらも __lshift__ で定義したとおりの結果となっている
<< 演算子なんかも自分で定義することができます。<< を定義すると Ruby の配列みたいですね。


__iadd__

__iadd__ は += 演算子に対応したメソッドです。
class Array(object):
    # ここに __init__ などの定義
    # ...

    def __iadd__(self, element):
        """+= 演算子を定義するメソッド
           values の各要素に渡された値を加算する """
        self.values = list(map(lambda x: x + element, self.values))
        return self

m1 = Array([3, 5])

m1 += 1
print(m1.values)
# => [4, 6]
# こちらも __iadd__ で定義したとおりの結果
# オブジェクト id も不変
こちらも定義したとおりに動いてくれています。ちなみに、戻り値はレシーバ自身にしないと( return self としておかないと)うまく動いてくれません。

以上です。

ここであげたのはごくごく一部です。演算子の挙動を定義できる特殊メソッドは豊富に用意されているので、どういうメソッドがあるのかは一度公式ドキュメントリファレンスをご覧になってみてください。


参考
Python 2.6 Quick Reference
3.4. Special method names - Python公式ドキュメント
Special Method Names - Dive Into Python 3
演算子のオーバーロード - とりあえず

0 件のコメント: