Python 如何用decorator类修饰实例方法?
考虑这个小例子:Python 如何用decorator类修饰实例方法?,python,class,self,python-decorators,Python,Class,Self,Python Decorators,考虑这个小例子: import datetime as dt class Timed(object): def __init__(self, f): self.func = f def __call__(self, *args, **kwargs): start = dt.datetime.now() ret = self.func(*args, **kwargs) time = dt.datetime.now(
import datetime as dt
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
class Test(object):
def __init__(self):
super(Test, self).__init__()
@Timed
def decorated(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return dict()
def call_deco(self):
self.decorated("Hello", world="World")
if __name__ == "__main__":
t = Test()
ret = t.call_deco()
哪张照片
Hello
()
{'world': 'World'}
为什么self
参数(应该是测试obj实例)没有作为第一个参数传递给修饰函数修饰
如果我手动执行,例如:
def call_deco(self):
self.decorated(self, "Hello", world="World")
它按预期工作。但是如果我必须事先知道函数是否被修饰,那么它就违背了修饰者的全部目的。这里的模式是什么,或者我误解了什么吗?你首先要理解 一旦你知道了这一点,“问题”就显而易见了:你正在用一个
Timed
实例-IOW,Test来装饰decordent
函数。decordent
是一个Timed
实例,不是函数
实例-并且您的Timed
类没有模仿函数
类型对描述符
协议的实现。您想要的是这样的:
import types
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, cls):
return types.MethodType(self, instance, cls)
你首先要明白 一旦你知道了这一点,“问题”就显而易见了:你正在用一个
Timed
实例-IOW,Test来装饰decordent
函数。decordent
是一个Timed
实例,不是函数
实例-并且您的Timed
类没有模仿函数
类型对描述符
协议的实现。您想要的是这样的:
import types
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, cls):
return types.MethodType(self, instance, cls)
tl;dr
您可以通过使定时
类a并从\uuuu get\uuuu
返回一个部分应用的函数来解决此问题,该函数将测试
对象作为参数之一,如下所示
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
实际问题 引用Python文档 decorator语法只是语法糖,以下两个函数定义在语义上是等价的:
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
所以,当你说
@Timed
def decorated(self, *args, **kwargs):
实际上是
decorated = Timed(decorated)
只有函数对象被传递到定时
,它实际绑定到的对象不会随它一起传递。所以,当你这样调用它时
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
self.func
将引用未绑定的函数对象,并以Hello
作为第一个参数调用它。这就是为什么self
打印为Hello
如何解决此问题? 由于在
Timed
中没有对Test
实例的引用,因此唯一的方法是将Timed
转换为描述符类。引用文件,第节
通常,描述符是一个具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖:\uuuu get\uuuuuuuuu()
、\uuuu set\uuuuuuu()
和\uuuu delete\uuuuuuuuuuu()
。如果这些方法中的任何一个是为对象定义的,则称之为描述符
属性访问的默认行为是从对象的字典中获取、设置或删除属性。例如,a.x
有一个查找链,从a.\uuuu dict\uuuuuuu['x']
开始,然后是type(a).\uuuu dict\uuuu['x']
,然后是type(a)
的基类,不包括元类
但是,如果查找的值是定义描述符方法之一的对象,那么Python可能会覆盖默认行为并调用描述符方法
我们只需定义这样一个方法,就可以将Timed
作为描述符
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
这里,self
指的是Timed
对象本身,instance
指的是正在进行属性查找的实际对象,owner
指的是与实例
对应的类
现在,当在Timed
上调用\uuuuuu call\uuuuu
时,将调用\uuu get\uuuu
方法。现在,不知何故,我们需要将第一个参数作为Test
类的实例传递(甚至在Hello
之前)。因此,我们创建另一个部分应用的函数,其第一个参数将是Test
实例,如下所示
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
现在,self.\uuu调用\uuu
是一个绑定方法(绑定到Timed
实例),而partial
的第二个参数是self.\uu调用\uu
调用的第一个参数
所以,所有这些都像这样有效地翻译
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
现在self.decordent
实际上是Timed(decordent)
(从现在起这将被称为TimedObject
)对象。每当我们访问它时,就会调用其中定义的\uuuu get\uuu
方法,并返回一个部分
函数。你可以这样确认
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
将打印
<functools.partial object at 0x7fecbc59ad60>
...
被翻译成
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
实际上是
TimedObject.__call__(<Test obj>, 'Hello', world="World")
TimedObject.\uuuuuuuuuuuuuuuuuuuuuuuuuuuu呼叫(,'Hello',world=“world”)
因此,
也成为*args
的一部分,当调用self.func
时,第一个参数将是
tl;dr
您可以通过使定时
类a并从\uuuu get\uuuu
返回一个部分应用的函数来解决此问题,该函数将测试
对象作为参数之一,如下所示
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
实际问题 引用Python文档 decorator语法只是语法糖,以下两个函数定义在语义上是等价的:
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
所以,当你说
@Timed
def decorated(self, *args, **kwargs):
实际上是
decorated = Timed(decorated)
只有函数对象被传递到定时
,它实际绑定到的对象不会随它一起传递。所以,当你这样调用它时
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
ret = self.func(*args, **kwargs)
def __get__(self, instance, owner):
...
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
t.call_deco()
self.decorated("Hello", world="World")
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
self.func
将引用未绑定的函数对象,并以Hello
作为第一个参数调用它。这就是为什么self
打印为Hello
如何