メソッドに出来るもの


個人的にfunctools.partialを気に入っています。

itertoolsもそうですが、Python関数型言語っぽく使えるので。



でも、残念ながら、functools.partialが返すものは厳密には関数じゃないみたいなんです。


前、どこかで見たのですが、メソッドにするときに、振る舞いの違いが露見するのです。



実際に確かめてみます。

from __future__ import with_statement, division, print_function
from functools import partial

class CallableClass(object):
    def __call__(*a):
        return a
    
class A(object):
    def m1(*a):
        return a
    
    m2 = lambda *a: a
    
    def f(*a):
        return a
    m3 = partial(f, 1)
    
    m4 = CallableClass()

a = A()
print(a.m1("hello!"))
print(a.m2("hello!"))
print(a.m3("hello!"))
print(a.m4("hello!"))

#実行結果
#(<__main__.A object at 0x00B04C50>, 'hello!')
#(<__main__.A object at 0x00B04C50>, 'hello!')
#(1, 'hello!')
#(<__main__.CallableClass object at 0x00B04C30>, 'hello!')


普通の関数のm1のと、lambdaのm2は、全く同じ振る舞いをしていることがわかります。


一方、functools.partialのm3、__call__を定義したクラスのm4には、


メソッドを呼び出されたインスタンス(普通selfにするやつ)が渡されていない事がわかります。


この差異が問題になるのは、関数デコレータをメソッドに対して使うとき。


普通の(メソッドではない)関数ならば、ぶっちゃけ呼び出し可能であれば、
真の関数でなくても問題になりません。


しかし、メソッド用のデコレータは、絶対に関数を返さないと、まずい事になります。



たとえば、こんなメモ化デコレータ

from __future__ import with_statement, division, print_function
class Memo(object):
    def __init__(self, func):
        self.func = func
        self.dict = {}
        
    def __call__(self, *a, **kw):
        key = tuple(a) + tuple(sorted(kw.iteritems()))
        if key in self.dict:
            return self.dict[key]
        else:
            value = self.func(*a, **kw)
            self.dict[key] = value
            return value

@Memo
def fib(n):
    if n in [0, 1]:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

class Fib(object):
    @Memo
    def fib(self, n):
        if n in [0, 1]:
            return 1
        else:
            return self.fib(n - 1) + self.fib(n - 2)

print(fib(30))
print(Fib().fib(30))