python-使用类作为方法装饰器

这个问题已经在这里有了答案:            >            How can I decorate an instance method with a decorator class?                                    3个
虽然有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__和签名以及类似的属性也应保留.

解决方法:

通常,当以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-是否可以在Django项目中重新定义反向?