2014/12/16

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でも特異メソッドを利用することはできます、というお話でした。


参考
python - Adding a Method to an Existing Object - Stack Overflow
Hoarded Homely Hints: Python Metaprogramming: Dynamically Adding Methods to Classes
Dynamically Adding a Method to Classes or Class Instances in Python - Ian Lewis

8.15. types — Names for built-in types — Python公式ドキュメント

0 件のコメント: