使用类作为方法装饰器[重复]

Posted

技术标签:

【中文标题】使用类作为方法装饰器[重复]【英文标题】:Using classes as method decorators [duplicate] 【发布时间】:2017-07-28 23:33:30 【问题描述】:

虽然有plenty of resources about using classes as decorators,但我还没有找到任何处理装饰问题的方法方法。这个问题的目标是解决这个问题。我将发布我自己的解决方案,但当然也邀请其他所有人发布他们的解决方案。


为什么“标准”实现不起作用

标准装饰器类实现的问题是python不会创建被装饰函数的绑定方法:

class Deco:
    def __init__(self, func):
        self.func= func
    
    def __call__(self, *args):
        self.func(*args)

class Class:
    @Deco
    def hello(self):
        print('hello world')

Class().hello() # throws TypeError: hello() missing 1 required positional argument: 'self'

方法装饰器需要克服这个障碍。


要求

使用上一个示例中的类,预计以下内容会起作用:

>>> i= Class()
>>> i.hello()
hello world
>>> i.hello
<__main__.Deco object at 0x7f4ae8b518d0>
>>> Class.hello is Class().hello
False
>>> Class().hello is Class().hello
False
>>> i.hello is i.hello
True

理想情况下,函数的__doc__ 和签名以及类似的属性也会被保留。

【问题讨论】:

也相关:Python decorator best practice, using a class vs a function 为什么要求它是一个类?装饰器只是一个函数有什么问题? @PaulRooney 在我的特殊情况下(我正在编写一个 GUI 库),我想在函数上存储一堆属性(如键盘热键、描述、类别等)以及一堆函数(如.start_in_new_thread().update_status())。我宁愿写一个包装类并完全替换函数,而不是将所有这些属性强制到函数上。 【参考方案1】:

通常当一个方法以some_instance.some_method() 访问时,python 的descriptor protocol 启动并调用some_method.__get__(),它返回一个绑定的方法。但是,由于该方法已被 Deco 类的实例替换,因此不会发生这种情况 - 因为 Deco 不是描述符。为了使Deco 按预期工作,它必须实现一个返回自身绑定副本的__get__ 方法。

实施

这是基本的“什么都不做”装饰器类:

import inspect
import functools
from copy import copy


class Deco(object):
    def __init__(self, func):
        self.__self__ = None # "__self__" is also used by bound methods

        self.__wrapped__ = func
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        # if bound to an object, pass it as the first argument
        if self.__self__ is not None:
            args = (self.__self__,) + args

        #== change the following line to make the decorator do something ==
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # create a bound copy
        bound = copy(self)
        bound.__self__ = instance

        # update __doc__ and similar attributes
        functools.update_wrapper(bound, self.__wrapped__)

        # add the bound instance to the object's dict so that
        # __get__ won't be called a 2nd time
        setattr(instance, self.__wrapped__.__name__, bound)

        return bound

要让装饰器做某事,请将您的代码添加到 __call__ 方法中。


这是一个带参数的:

class DecoWithArgs(object):
    #== change the constructor's parameters to fit your needs ==
    def __init__(self, *args):
        self.args = args

        self.__wrapped__ = None
        self.__self__ = None

    def __call__(self, *args, **kwargs):
        if self.__wrapped__ is None:
            return self.__wrap(*args, **kwargs)
        else:
            return self.__call_wrapped_function(*args, **kwargs)

    def __wrap(self, func):
        # update __doc__ and similar attributes
        functools.update_wrapper(self, func)

        return self

    def __call_wrapped_function(self, *args, **kwargs):
        # if bound to an object, pass it as the first argument
        if self.__self__ is not None:
            args = (self.__self__,) + args

        #== change the following line to make the decorator do something ==
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # create a bound copy of this object
        bound = copy(self)
        bound.__self__ = instance
        bound.__wrap(self.__wrapped__)

        # add the bound decorator to the object's dict so that
        # __get__ won't be called a 2nd time
        setattr(instance, self.__wrapped__.__name__, bound)
        return bound

这样的实现让我们可以在方法和函数上使用装饰器,所以我认为它应该被认为是好的做法。

【讨论】:

以上是关于使用类作为方法装饰器[重复]的主要内容,如果未能解决你的问题,请参考以下文章

用参数继承类方法装饰器[重复]

在另一个类中使用实例方法作为装饰器

用类作为装饰器装饰函数!

了解使基于类的装饰器支持实例方法的技术[重复]

“@”装饰器(在 Python 中)[重复]

使用装饰器作为类来装饰 Python 类