如何用装饰器类装饰实例方法? [英] How can I decorate an instance method with a decorator class?

查看:84
本文介绍了如何用装饰器类装饰实例方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个小例子:

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实例)没有作为第一个参数传递给装饰函数

Why is the self parameter (which should be the Test obj instance) not passed as first argument to the decorated function decorated?

如果我手动进行操作,例如:

If I do it manually, like :

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

它按预期工作。但是,如果我必须事先知道某个函数是否装饰,它就破坏了装饰器的全部目的。

it works as expected. But if I must know in advance if a function is decorated or not, it defeats the whole purpose of decorators. What is the pattern to go here, or do I misunderstood something?

推荐答案

tl; dr

您可以通过将 Timed 类设为描述符,然后从 __ get __ 返回部分应用的函数,该函数将应用 Test 对象作为参数之一,例如

You can fix this problem by making the Timed class a descriptor and returning a partially applied function from __get__ which applies the Test object as one of the arguments, like this

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文档 装饰器

Quoting Python documentation for decorator,


装饰器语法只是语法糖,以下两个函数定义在语义上是等效的:

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)

仅将函数对象传递给计时该对象实际绑定到的对象不会随之传递。因此,当您像这样调用它时

only the function object is passed to the Timed, the object to which it is actually bound is not passed on along with it. So, when you invoke it like this

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

self.func 将引用未绑定的函数对象,它是用 Hello 作为第一个参数调用。这就是为什么 self 打印为 Hello 的原因。

self.func will refer to the unbound function object and it is invoked with Hello as the first argument. That is why self prints as Hello.

如何解决此问题?

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

Since you have no reference to the Test instance in the Timed, the only way to do this would be to convert Timed as a descriptor class. Quoting the documentation, Invoking descriptors section,


通常,描述符是具有绑定行为的对象属性,其属性访问已被描述符协议中的方法覆盖: __ get __() __ set __() __ delete __()。如果为对象定义了这些方法中的任何一种,则将其称为描述符。

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.

属性访问的默认行为是获取,设置或删除属性。从对象的字典中。例如, ax 的查找链以 a .__ dict __ ['x'] 开头,然后是 type(a).__ dict __ ['x'] ,并继续通过 type(a)的基类,但不包括元类。

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.

但是,如果查找的值是定义描述符方法之一的对象,则Python可能会覆盖默认行为并改为调用描述符方法

我们可以通过简单地定义一个方法使 Timed 成为描述符。像这样

We can make Timed a descriptor, by simply defining a method like this

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

在这里,自我是指 Timed 对象本身, instance 指发生属性查找的实际对象,而 owner 指的是到与实例对应的类。

Here, self refers to the Timed object itself, instance refers to the actual object on which the attribute lookup is happening and owner refers to the class corresponding to the instance.

现在,当 __ call __ 计时上调用, __ get __ 方法将被调用。现在,以某种方式,我们需要将第一个参数作为 Test 类的实例传递(甚至在 Hello 之前)。因此,我们创建了另一个部分应用的函数,其第一个参数将是 Test 实例,例如

Now, when __call__ is invoked on Timed, the __get__ method will be invoked. Now, somehow, we need to pass the first argument as the instance of Test class (even before Hello). So, we create another partially applied function, whose first parameter will be the Test instance, like this

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

现在, self .__ call __ 是一个绑定方法(绑定到 Timed 实例)和 partial 的第二个参数是 self .__ call __ 调用的第一个参数。

Now, self.__call__ is a bound method (bound to Timed instance) and the second parameter to partial is the first argument to the self.__call__ call.

因此,所有这些有效的翻译都是这样的

So, all these effectively translate like this

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

现在 self.decorated 实际上是 Timed(decorated)(这将称为 TimedObject )。每当我们访问它时,在其中定义的 __ get __ 方法将被调用,并返回 partial 函数。您可以确认像这样

Now self.decorated is actually Timed(decorated) (this will be referred as TimedObject from now on) object. Whenever we access it, the __get__ method defined in it will be invoked and it returns a partial function. You can confirm that like this

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>

So, <Test obj> also becomes a part of *args, and when self.func is invoked, the first argument will be the <Test obj>.

这篇关于如何用装饰器类装饰实例方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆