Raf*_*l T 32 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() - 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()
Run Code Online (Sandbox Code Playgroud)
打印
Hello
()
{'world': 'World'}
Run Code Online (Sandbox Code Playgroud)
为什么self参数(应该是Test obj实例)不作为第一个参数传递给装饰函数decorated?
如果我手动完成,例如:
def call_deco(self):
self.decorated(self, "Hello", world="World")
Run Code Online (Sandbox Code Playgroud)
它按预期工作.但是如果我必须事先知道函数是否装饰,它就会破坏装饰器的整个目的.这里的模式是什么,或者我误解了什么?
the*_*eye 44
TL;博士
您可以通过将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)
Run Code Online (Sandbox Code Playgroud)
实际问题
引用装饰器的 Python文档,
装饰器语法只是语法糖,以下两个函数定义在语义上是等价的:
Run Code Online (Sandbox Code Playgroud)def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
所以,当你说,
@Timed
def decorated(self, *args, **kwargs):
Run Code Online (Sandbox Code Playgroud)
实际上
decorated = Timed(decorated)
Run Code Online (Sandbox Code Playgroud)
只有函数对象传递给Timed,给它实际上是绑定的对象不与它一起传递.所以,当你像这样调用它时
ret = self.func(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
self.func将引用未绑定的函数对象,并将其Hello作为第一个参数调用.这就是为什么self打印为Hello.
我怎样才能解决这个问题?
由于您没有引用该Test实例Timed,唯一的方法是转换Timed为描述符类.引用文档,调用描述符部分,
一般而言,描述符是用"结合行为",一个属性的访问已被描述符协议方法重写对象属性:
__get__(),__set__(),和__delete__().如果为对象定义了任何这些方法,则称其为描述符.属性访问的默认行为是从对象的字典中获取,设置或删除属性.例如,
a.x有一个查找链,从a.__dict__['x'],然后type(a).__dict__['x'],继续通过type(a)排除元类的基类开始.但是,如果查找的值是定义其中一个描述符方法的对象,则Python可以覆盖默认行为并调用描述符方法.
我们可以Timed通过简单地定义这样的方法来创建描述符
def __get__(self, instance, owner):
...
Run Code Online (Sandbox Code Playgroud)
这里,self指的是Timed对象本身,instance指的是发生属性查找的实际对象,并owner引用对应的类instance.
现在,当__call__调用on时Timed,__get__将调用该方法.现在,不知何故,我们需要将第一个参数作为Test类的实例传递(甚至之前Hello).因此,我们创建另一个部分应用的函数,其第一个参数将是Test实例,就像这样
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
Run Code Online (Sandbox Code Playgroud)
现在,self.__call__是绑定方法(绑定到Timed实例),第二个参数partial是self.__call__调用的第一个参数.
所以,所有这些都有效地翻译成这样
t.call_deco()
self.decorated("Hello", world="World")
Run Code Online (Sandbox Code Playgroud)
现在self.decorated实际上Timed(decorated)(这TimedObject将从现在开始称为)对象.每当我们访问它时,__get__将调用其中定义的方法并返回一个partial函数.你可以这样确认
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
Run Code Online (Sandbox Code Playgroud)
会打印
<functools.partial object at 0x7fecbc59ad60>
...
Run Code Online (Sandbox Code Playgroud)
所以,
self.decorated("Hello", world="World")
Run Code Online (Sandbox Code Playgroud)
被翻译成
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
Run Code Online (Sandbox Code Playgroud)
既然我们返回一个partial函数,
partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))
Run Code Online (Sandbox Code Playgroud)
这实际上是
TimedObject.__call__(<Test obj>, 'Hello', world="World")
Run Code Online (Sandbox Code Playgroud)
因此,<Test obj>也成为其中的一部分*args,并且在self.func调用时,第一个参数将是<Test obj>.
您首先必须了解函数如何成为方法以及如何self"自动"注入.
一旦你知道了,"问题"是显而易见的:你正在decorated用一个Timed实例来装饰函数- IOW,Test.decorated是一个Timed实例,而不是一个function实例 - 你的Timed类不会模仿function类型的descriptor协议实现.你想要的是这样的:
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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
10580 次 |
| 最近记录: |