静かなる名辞

pythonとプログラミングのこと


【python】pythonで動的にメソッドを追加する

前置き

 この辺りの話、以前からちょっとモヤモヤしていたので、この際実験してすっきりさせておきます。

はじめに

 そもそも、pythonのメソッドは関数オブジェクト(もどき)のはずです。

 ということは、クラス定義構文を使わなくても生成する手段があるはずです。

 というあたりを、実験して確かめていこうと思います。

 なお、以下の内容はpython3が前提です。python2は微妙にクラス周りの仕様が違っています。

staticmethod

 まず、問題の少なさそうなstaticmethodから試します。

>>> class Hoge:  # 比較対象
...     @staticmethod
...     def hoge():
...         print("hoge")
... 
>>> Hoge.hoge()
hoge
>>> type(Hoge.hoge)  # 型を確認しておく
<class 'function'>
>>> class Hoge:  # 動的に追加する対象
...     pass
... 
>>> @staticmethod
... def hoge():
...     print("hoge")
... 
>>> Hoge.hoge = hoge
>>> Hoge.hoge()
hoge
>>> type(Hoge.hoge)
<class 'function'>

 えっと、できたっぽい。僕の目には区別がつきません。よしとします。

classmethod

 classmethodではどうか。

>>> class Hoge:  # 比較対象
...     @classmethod
...     def printname(cls):
...         print(cls.__name__)
... 
>>> Hoge.printname()
Hoge
>>> type(Hoge.printname)
<class 'method'>
>>> class Hoge:  # 動的に追加する対象
...     pass
... 
>>> @classmethod
... def printname(cls):
...     print(cls.__name__)
... 
>>> Hoge.printname = printname
>>> Hoge.printname()
Hoge
>>> type(Hoge.printname)
<class 'method'>

 これもできてると思います。

インスタンスメソッド

 これは難しいんじゃないの? と思っているインスタンスメソッドです。

 とりあえず先に、型を見ることにします。

>>> class Hoge:
...     def __init__(self, name):
...         self.name = name
...     def printnamehoge(self):
...         print(self.name, "hoge")
... 
>>> h = Hoge("taro")
>>> h.printnamehoge()
taro hoge
>>> type(Hoge.printnamehoge)
<class 'function'>
>>> type(h.printnamehoge)
<class 'method'>

 クラス配下のprintnamehogeはfunction、インスタンス配下のprintnamehogeはmethodという型になっていることがわかります。

 これを踏まえて、まずクラスに関数を追加してみます。

>>> def f(self):
...     print(self.name, "fuga")
... 
>>> Hoge.printnamefuga = f
>>> h = Hoge("taro")
>>> h.printnamehoge()
taro hoge
>>> h.printnamefuga()
taro fuga

 できました。ちょっと感動。

 次に、インスタンスに追加してみます。method型なので、うまくいくものだろうか。

>>> def p(self):
...     print(self.name, "piyo")
... 
>>> h.printnamepiyo = p
>>> h.printnamepiyo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: p() missing 1 required positional argument: 'self'

 案の定うまくいかない。

 こんな感じでmethodのhelpを調べます。

>>> help(type(h.printnamehoge))
Help on class method in module builtins:

class method(object)
 |  method(function, instance)  # こうやって使えということ
 |  
 |  Create a bound instance method object.
 |  
 |  Methods defined here:
 |  
# 長いので省略

 使い方はわかりました。ただ、methodはどこからimportすれば良いのか(というかそもそもimportできるのか)わかりません。

 だけど、上のようにtype(h.printnamehoge)で取り出せているので、実は困らないのです。

 名前があると嬉しいので、こんな感じで使いやすくしておきます。

>>> method = type(h.printnamehoge)

 あとはhelpの通り使って呼び出します。動くかな。

>>> h.printnamepiyo = method(p, h)
>>> h.printnamepiyo()
taro piyo

 動いた!

 やった。これで、pythonのメソッドはすべて動的に追加できるとわかりました。

考察のようなもの

 こういう性質があるということは、pythonは本質的にクラスベースのオブジェクト指向言語とは一線を画しています。むしろインスタンスベース(プロトタイプベース)のように思えます。

 クラス構文ができた最近のjavascriptみたいなものです。クラス構文は一種の糖衣構文ということでしょう。本当のところ、きっとすべてが動的に行われているのです。

 なお、クラスオブジェクト自体は以下のようにインスタンス化できるようです。

>>> class Hoge:
...     pass
... 
>>> Hoge
<class '__main__.Hoge'>
>>> type("Hoge", Hoge.__bases__, dict(Hoge.__dict__))  # 同じものを作る
<class '__main__.Hoge'>

 後はこれにアレコレしてメソッドを付け加えていける。インスタンス化はクラスの__call__が呼ばれた後の処理に委ねられるはずで、これは自動的に生成されるのでちょっと暗黙的ですが、インスタンスメソッドの項目で記述したようなboundがその中で行われているはずです。

 いや、素敵ですねー。

まとめ

 すべて動的に行えることがわかって嬉しいと思いました。

 これを使って実用的なコードを書くのは正気の沙汰ではないので、やめてください。