简而言之
您可以通过将Timed
类作为描述符并从__get__
返回一个部分应用的函数来解决此问题,该函数将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)
实际问题
引用Python文档中装饰器的说法,
The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
所以,当你说:
@Timed
def decorated(self, *args, **kwargs):
它实际上是
decorated = Timed(decorated)
只有函数对象被传递到Timed
,实际绑定的对象并没有随之传递。因此,当您像这样调用它时:
ret = self.func(*args, **kwargs)
self.func
将指向未绑定的函数对象,并使用Hello
作为第一个参数调用。这就是为什么self
打印为Hello
的原因。
我该如何修复这个问题?
由于在“定时”中没有对“测试”实例的引用,唯一的方法是将“定时”转换为
描述符类。引用文档中的
调用描述符部分。
一般来说,描述符是一个具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法所覆盖:__get__()、__set__()和__delete__()。如果任何这些方法都为一个对象定义了,那么它就被称为描述符。
属性访问的默认行为是从对象字典中获取、设置或删除属性。例如,a.x具有以a.__dict__['x']开始的查找链,然后是type(a).__dict__['x'],并且继续通过排除元类的type(a)的基类。
但是,如果查找到的值是定义了描述符方法之一的对象,则Python可能会覆盖默认行为并调用描述符方法。
我们可以通过简单地定义以下方法来使Timed成为描述符。
def __get__(self, instance, owner):
...
这里,“self”指的是“Timed”对象本身,“instance”指的是属性查找发生的实际对象,“owner”指的是与“instance”相对应的类。
现在,当在“Timed”上调用“__call__”时,“__get__”方法将被调用。现在,我们需要以“Test”类的实例作为第一个参数(甚至在“Hello”之前)传递。因此,我们创建另一个部分应用的函数,其第一个参数将是“Test”实例,如下所示。
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
现在,
self.__call__
是一个绑定方法(绑定到
Timed
实例),而
partial
的第二个参数是传递给
self.__call__
调用的第一个参数。
因此,所有这些都可以这样有效地翻译:
t.call_deco()
self.decorated("Hello", world="World")
现在,
self.decorated
实际上是一个
Timed(decorated)
对象(从现在开始将其称为
TimedObject
)。每当我们访问它时,其中定义的
__get__
方法将被调用,并返回一个
partial
函数。您可以通过以下方式确认:
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
将会打印。
<functools.partial object at 0x7fecbc59ad60>
...
因此,
self.decorated("Hello", world="World")
被翻译为
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
由于我们返回了一个partial
函数,
partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))
实际上是
TimedObject.__call__(<Test obj>, 'Hello', world="World")
因此,
<Test obj>
也成为了
*args
的一部分,当调用
self.func
时,第一个参数将是
<Test obj>
。