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


如何