gpt4 book ai didi

python - 如何用装饰器类装饰实例方法?

转载 作者:IT老高 更新时间:2023-10-28 22:08:07 27 4
gpt4 key购买 nike

考虑这个小例子:

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 参数(应该是 Test obj 实例)没有作为第一个参数传递给修饰函数 decorated

如果我手动操作,例如:

def call_deco(self):
self.decorated(self, "Hello", world="World")

它按预期工作。但是如果我必须提前知道一个函数是否被装饰,它就违背了装饰器的全部目的。这里的模式是什么,还是我误解了什么?

最佳答案

tl;dr

您可以通过设置 Timed 来解决此问题。甲级descriptor并从 __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)

实际问题

引用 decorator 的 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 , 它实际绑定(bind)的对象不会随之传递。所以,当你像这样调用它时

ret = self.func(*args, **kwargs)

self.func将引用未绑定(bind)的函数对象,并使用 Hello 调用它作为第一个论点。这就是为什么self打印为 Hello .


我该如何解决这个问题?

由于您没有引用 Test Timed 中的实例, 唯一的方法是转换 Timed作为一个描述符类。引用文档,Invoking descriptors部分,

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.

However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead.

我们可以制作Timed一个描述符,通过简单地定义一个这样的方法

def __get__(self, instance, owner):
...

这里,selfTimed对象本身,instance指发生属性查找的实际对象,owner指与instance 对应的类.

现在,当 __call__Timed 上调用, __get__方法将被调用。现在,不知何故,我们需要将第一个参数作为 Test 的实例传递。类(甚至在 Hello 之前)。因此,我们创建另一个部分应用函数,其第一个参数将是 Test例如,像这样

def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)

现在,self.__call__是绑定(bind)方法(绑定(bind)到 Timed 实例)和第二个参数到 partialself.__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> .

关于python - 如何用装饰器类装饰实例方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30104047/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com