引言
在學(xué)習(xí)裝飾器類方法時發(fā)現(xiàn)忙菠,如果裝飾器類只有__ call __方法則只能裝飾普通的函數(shù)(function),不能對實例方法(instance method)進行裝飾坏瞄,經(jīng)過研究發(fā)現(xiàn)與python的函數(shù)實現(xiàn)有關(guān)仁堪。本文就此展開討論媳纬,并給出一個完整的裝飾器類實現(xiàn)方法球匕。
問題發(fā)現(xiàn)
我們都知道纹磺,當(dāng)我們調(diào)用一個類的實例方法時,實例會作為方法的第一個入?yún)elf亮曹。而在下面的例子中橄杨,實例方法foo被裝飾器類Decorator裝飾后秘症,Decorated().foo('hello, world')的第一個入?yún)elf并不是Decorated實例,而是我們傳進去的"hello, world"式矫,很明顯這是不符合我們預(yù)期的乡摹。實際上這與python的函數(shù)調(diào)用有關(guān),bound method(通過實例調(diào)用的方法)會自動將實例本身作為方法的第一個入?yún)⒉勺鴉unction和unbound method(類直接調(diào)用的方法)不會自動注入實例趟卸。當(dāng)我們用裝飾器裝飾后,被傳入Decorator中的func失去了bound屬性氏义,自然也不會自動注入實例。
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Decorator: self: {}, args: {}, kwargs: {}".format(self, args, kwargs))
return self.func(*args, **kwargs)
class Decorated(object):
@Decorator
def foo(self, *args, **kwargs):
print("Method Decorated: self: {}, args: {}, kwargs: {}".format(self, args, kwargs))
Decorated().foo('hello, world')
# Decorator: self: <__main__.Decorator object at 0x7f978aeb0290>, args: ('hello, world',), kwargs: {}
# Method Decorated: self: hello, world, args: (), kwargs: {}
function图云、bound method與unbound method的區(qū)別
上一節(jié)我們說到了bound method和unboud method惯悠,本小節(jié)將對這些概念進行具體說明。
class Cls(object):
def foo(self):
pass
instance = Cls()
print 'foo' in Cls.__dict__ # True
print 'foo' in instance.__dict__ # False
print Cls.foo # <unbound method Cls.foo>
print instance.foo # <bound method Cls.foo of <__main__.Cls object at 0x7ff05ddef390>>
print Cls.__dict__['foo'] # <function foo at 0x7ff05dde98c0>
我們發(fā)現(xiàn)竣况,foo是以function類型存在類的__dict__
中的克婶,但通過實例訪問是會得到一個bound method,通過類訪問是會得到一個unbound method丹泉。這是因為在python中情萤,所有的函數(shù)都是非資料描述器(no data descriptor)。通過類或?qū)嵗L問時摹恨,會被__get__
方法會包裝一層返回unbound method或bound method筋岛,而unboud和bound的區(qū)別在于method的im_self是否為空。bound method會自動將instance作為第一個入?yún)⑸购澹鴘nbound method對參數(shù)不做任何處理睁宰。
在上一小節(jié)的例子中,foo作為Decorator的一個成員變量寝凌,其type是一個function柒傻,所以不會自動地注入實例。
Cls.foo會被翻譯成
Cls.__dict __[‘foo’].__get__(None, Cls)
较木,返回一個unbound method(im_self is None)
instance.foo會被翻譯成type(instance).__dic __[‘foo’].__get__(instance, type(instance))
红符,返回一個bound method(im_self == instance)
instance.foo(args, kwargs)
等價于Cls.foo(instance, args, kwargs)
修改方法
通過分析,我們發(fā)現(xiàn)根本原因在于伐债,被裝飾的foo作為Decorator實例的成員變量预侯,其類型是一個function,失去了bound屬性泳赋,不能自動地將instance實例注入到函數(shù)的第一個參數(shù)中去雌桑。為了解決這個,我們可以將Decorator實現(xiàn)成一個描述器祖今,當(dāng)被類或?qū)嵗L問時校坑,通過types.MethodType將Decorator實例包裝成一個method拣技。
class Decorator(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return types.MethodType(self, instance, owner)
def __call__(self, *args, **kwargs):
print("Decorator: self: {}, args: {}, kwargs: {}".format(self, args, kwargs))
return self.func(*args, **kwargs)
參考文獻
The Inside Story on New-Style Classes
python - Instancemethod or function? - Stack Overflow
Chris’s Wiki :: blog/python/HowFunctionsToMethods
Descriptor HowTo Guide — Python 2.7.15 documentation
python - How can I decorate an instance method with a decorator class? - Stack Overflow
Glossary — Python 2.7.15 documentation