Python Tips: 特異メソッドを作りたい

Pythonで「特異メソッド」を作る方法をご紹介します。

特異メソッドというのは(私が知るかぎり)Ruby発祥のことばで、「特定のオブジェクトだけが持つメソッド」のこと。インスタンスメソッドは特定のクラスのインスタンスであればどのインスタンスからも呼び出すことができますが、特異メソッドはある特定のインスタンスからしか呼び出すことができません。

types ライブラリの MethodType というコンストラクタを使えばPythonでも特異メソッドを作ることができます。

具体的に見ていきます。

# ライブラリの読み込み
from types import MethodType

class Dog(object):
    def __init__(self, name):
        self.name = name

d1 = Dog(“inu”)

# 以下で特異メソッドを追加していきます
# まずは特異メソッドにしたい処理を関数として用意
def hello(self):
    print%s: bow!” % self.name

# 特異メソッドとしてオブジェクトのアトリビュートに追加
# format: MethodType(method, obj)
d1.hello = MethodType(hello, d1)

d1.hello()  # => inu: bow!
# d1 をレシーバに hello メソッドが呼び出せるようになる

d1 だけで使えるインスタンスメソッドを追加することができました。

hello は d1 だけに追加したアトリビュートなので、他のインスタンスで利用することはできません。

d2 = Dog(“hayato”)
d2.hello()  # => AttributeError
# hello はあくまでも d1 のメソッドなので他のインスタンスからは呼び出せない

ちなみに、MethodType を使わず

d1.hello = hello

とやっても一見追加できるように思いますが、これだとただの関数となってしまい、関数内でレシーバである d1 への参照を持つことができません。

また、次のようにやってもうまく行きません。

d1.__class__.hello = hello

これだと hello はクラス全体で共有されるインスタンスメソッドになるため、こちらの方法も特異メソッドを作るには不適切です。

以上です。

ちなみに、次の部分はハードコーディングしてしまっているので、再利用性が高くありません。

d1.hello = MethodType(hello, d1)

他でも使うことを考えてもっときれいに書くなら次のような感じになるでしょうか。

def add_eigen_method(obj, method):
    setattr(obj, method.__name__, MethodType(method, obj)

add_eigen_method(d1, hello)

Rubyほどシンプルにきれいに書くことはできませんが、Pythonでも特異メソッドを利用することはできます、というお話でした。

参考