読者です 読者をやめる 読者になる 読者になる

ゆくゆくは有へと

おかゆ/オカ∃/大鹿有生/彼ノ∅有生 の雑記

関数がディスクリプタであることを利用してインスタンスに動的にメソッドを追加する

だからタイトル長くない?

kk6.hateblo.jp

types.MethodType 使えば個々のインスタンスにメソッドを追加できるんだけど、typesをわざわざ import するのもなって感じにもなる。

で、

qiita.com

を読んでたら、関数オブジェクト(こいつは非データディスクリプタ)の__get__内でtypes.MethodTypeがよしなに呼び出されているのを見つけたので、これを使えばいいなと思いました。

class Klass():
    def __init__(self):
        self.value = 10


def meth(self, value):
    self.value += value
    return self.value


if __name__ == '__main__':
    c = Klass()
    c.meth = meth.__get__(c)
    print(c.value) # => 10
    print(c.meth(5)) # => 15
    print(c.__dict__) # => {'meth': <bound method meth of <__main__.Klass object at 0x000001994F40D9E8>>, 'value': 15}

ちょっと面白いのは、当たり前だけど、こうやって追加すると、クラスに定義されたメソッドではないから、インスタンスの属性辞書に直接メソッド(バウンドメソッド)が登録されるってことですね。まあ逆に言えば、いつもはインスタンスの属性辞書にメソッドは登録されておらず、あくまでクラスの属性を引っ張ってきてる(そのときにディスクリプタによってselfがバウンドされる)ってことですね。先にバウンドしておくか、呼び出し時にその都度バウンドしてもらうかってところが異なります。

ところで、メソッド(バウンドメソッドも)は新たに属性を加えられないっぽいですが、関数オブジェクトには色々属性を追加できます。

    Klass.hoge.atr = 100
    print(Klass.hoge.atr)
    print(c.hoge.atr) # => 100

クラスでメソッドを定義した場合、クラスからアクセスすればそれはただの関数オブジェクトなので、上の例みたいに新たに属性を追加できますが、インスタンスにバウンドメソッドをもたせた場合はそういうことができません(まあそんな使う性質でもないし、ほとんど差はないって思っていいとは思うけど)。