インスタンスに動的にメソッドを追加する

追記:
記事を公開してからおんなじような記事あったりするかなと思ってググったら2年前にIanさんがほとんど同じ内容書いてた。
Pythonでメソッドをクラスまたはインスタンスに動的に追加する - Ian Lewis
しかも僕その記事はてブしてたよ…

Interactive Shell で色々試してる時なんかにインスタンスにメソッドを追加したくなる時がある。

>>> class Person(object):
...     def __init__(self, name):
...         self._name = name
...
>>> alice = Person("Alice")
>>> bob = Person("Bob")

こんなクラスがあって、幾つかインスタンスを作っているとする。で、self._nameを取得するメソッドが欲しくなったとしよう。

>>> def get_name(self):
...     return self._name

これを単純にインスタンスのメンバーとして代入してもうまくいかない

>>> alice.get_name = get_name
>>> alice.get_name()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: get_name() takes exactly 1 argument (0 given)

これはalice.get_nameがただの関数だから。インスタンスメソッドではない。

>>> type(alice.get_name)
<type 'function'>

コレジャナイので一旦消します。

>>> del alice.get_name

さて、じゃあどうすればいいかというと

>>> Person.get_name = get_name

というようにクラスの方に追加してやる。ただ、これだと当然ながら全てのインスタンスに影響が出る

>>> alice.get_name()
'Alice'
>>> bob.get_name()
'Bob'

影響が出るというか、実はaliceもbobもget_nameなんて持ってない。持ってないんだけど、クラスの方を見に行ったらそっちにあったからそれを呼び出してるだけ。

>>> del alice.get_name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: get_name
>>> alice.__dict__
{'_name': 'Alice'}

というわけでまあこれでいいんだけど、もしある特定のインスタンスにだけ設定したいなんて場合にはどうすればいいか…というのが今回の本題。結論としては、types.MethodTypeを使う。

>>> del Person.get_name
>>> import types
>>> alice.get_name = types.MethodType(get_name, alice)
>>> alice.get_name()
'Alice'
>>> bob.get_name()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'get_name'

第一引数にメソッドとして登録したい関数、第二引数にインスタンスを渡してやると、それらを紐付けてくれる。これでaliceはget_nameメソッドを持ってるけど、bobは持っていないという状態にすることができる。用途が思いつかないけど気になったから調べてみただけ。