2014/06/10

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

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

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

特殊メソッドというのは あらかじめ決められたタイミングで呼び出される特別なメソッド のことです。メソッド名を指定して呼び出さなくても、オブジェクトのライフサイクルの中の特定のタイミングで自動的に呼び出されます。ことばでの説明を聞くとなんやらよくわかりませんが、具体例を見るとわかりやすいと思います。

特殊メソッドの代表は __init__() メソッドです。自作のクラスに __init__() メソッドを定義しておけば、インスタンスの生成時に __init__() が自動で呼び出されます。

Python では、特殊メソッドを定義することによって自作クラスの演算子の振る舞いをある程度自由に決めることができます。

以下その方法を具体的に見ていきます。演算子関係の特殊メソッドは割とたくさんありますがロジックは共通なので、次の 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__()* 演算子に対応したメソッドです。 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 3:

Python 2:

参考

0 件のコメント: