为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

白板的微信 2019-11-02 09:52:22

考虑这个小例子:


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")

它按预期工作。但是,如果我必须事先知道某个函数是否装饰,它就破坏了装饰器的全部目的。这里的模式是什么,还是我误会了什么?


查看完整描述

3 回答

?
慕容3067478

您可以通过将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文档作为decorator,


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


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。


我怎样才能解决这个问题?


由于您在中没有引用Test实例Timed,因此唯一的方法是将其转换Timed为描述符类。引用文档,“ 调用描述符”部分,


一般而言,描述符是用“结合行为”,一个属性的访问已被描述符协议方法重写对象属性:__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。


现在,在__call__上调用时Timed,该__get__方法将被调用。现在,以某种方式,我们需要将第一个参数传递为Testclass 的实例(甚至在之前Hello)。因此,我们创建了另一个部分应用的函数,其第一个参数将是Test实例,如下所示


def __get__(self, instance, owner):

    from functools import partial

    return partial(self.__call__, instance)

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


所以,所有这些都像这样有效地翻译


t.call_deco()

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

Now 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>。


查看完整回答
反对 2019-11-02
  • 慕用0135624
    慕用0135624
    您好,我也遇到了他的问题,看来您的答案后获得了启发,但还有一点不清楚,如果在Timed类中的__init__里面定义一个列表,用来存放所装饰函数的时间,之后打算用来求出最小值,最大值与平均值,那么我该如何获得这个列表?
?
慕码人2483693

您首先必须了解函数如何成为方法以及如何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)


查看完整回答
反对 2019-11-02
?
慕慕0277861

我通过以下方式使用装饰器:


def timeit(method):

    def timed(*args, **kw):

        ts = time.time()

        result = method(*args, **kw)

        te = time.time()

        ts = round(ts * 1000)

        te = round(te * 1000)

        print('%r (%r, %r) %2.2f millisec' %

             (method.__name__, args, kw, te - ts))

        return result

    return timed



 class whatever(object):

    @timeit

    def myfunction(self):

         do something


查看完整回答
反对 2019-11-02

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信