装饰器类和缺少必需的位置参数

and*_*iev 6 python callable decorator python-decorators

我在包装类方面遇到问题,并且无法弄清楚我做错了什么。我如何让该包装器与带有“self”参数的任何类函数一起使用?

这是针对 Python 3.7.3 的。问题是我记得包装器以前工作过,但似乎有些东西发生了变化……也许我现在只是做了一些错误的事情,而以前没有。

class SomeWrapper:

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # this fails because self is not passed
        # ERROR: __init__() missing 1 required positional argument: 'self'
        func_ret = self.func(*args, **kwargs)

        # this is also wrong, because that's the wrong "self"
        # ERROR: 'SomeWrapper' object has no attribute 'some_func'
        # func_ret = self.func(self, *args, **kwargs)

        return func_ret


class SomeClass:

    SOME_VAL = False

    def __init__(self):
        self.some_func()
        print("Success")

    @SomeWrapper
    def some_func(self):
        self.SOME_VAL = True

    def print_val(self):
        print(self.SOME_VAL)


SomeClass().print_val()
Run Code Online (Sandbox Code Playgroud)

jsb*_*eno 6

因此,在 python 3 中,方法声明作为方法工作,当它们只是在类体内定义为函数时,会发生的情况是该语言使用“描述符协议”。

简而言之,普通方法只是一个函数,直到从实例中检索它为止:由于该函数有一个方法__get__,因此它们被识别为描述符,并且该__get__方法负责返回一个“部分函数”,该“部分函数”是“绑定方法”,self调用时会插入参数。如果没有__get__方法,则从实例检索实例SomeWrapper时,没有有关该实例的信息。

简而言之,如果要对方法使用基于类的装饰器,则不仅需要编写__call__,还需要编写__get__方法。这应该足够了:


from copy import copy

class SomeWrapper:

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
 
        func_ret = self.func(self.instance, *args, **kwargs)

        return func_ret

    def __get__(self, instance, owner):
        # self here is the instance of "somewrapper"
        # and "instance" is the instance of the class where
        # the decorated method is.
        if instance is None:
            return self
        bound_callable = copy(self)
        bound_callable.instance = instance
        return self

Run Code Online (Sandbox Code Playgroud)

除了复制装饰器实例之外,这也可以工作:

from functools import partial

class SomeWrapper:
   ...
   
   def __call__(self, instance, *args, **kw):
       ...
       func_ret = self.func(instance, *args, **kw)
       ...
       return func_ret

   def __get__(self, instance, owner):
       ...
       return partial(self, instance)
Run Code Online (Sandbox Code Playgroud)

“部分”和 self 的副本都是可调用的,它们“知道”它们来自哪个实例__got__

只需self.instance 在装饰器实例中设置属性并返回self也可以,但仅限于一次使用该方法的单个实例。在具有一定并行性的程序中,或者即使代码会检索一个方法来延迟调用它(例如将其用于回调),它也会以一种壮观且难以调试的方式失败,因为该方法将接收另一个实例它的“self”参数。