【问题标题】:Decorator class and missing required positional arguments装饰器类和缺少必需的位置参数
【发布时间】:2019-11-26 22:28:12
【问题描述】:

我在使用包装类时遇到问题,无法弄清楚我做错了什么。 如何让该包装器与任何带有“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()

【问题讨论】:

  • __call__ 方法应该返回一个可调用对象,而不是调用可调用对象的结果。装饰器应该是它的一个实例,而不是类。
  • 不只是装饰器函数的情况吗?我的印象是你可以在 __call__ 中调用装饰函数。
  • 是的——在这方面你是对的——克劳斯弄错了。
  • 将首先调用该类,并将要装饰的方法作为“func”参数传递给__init__。结果实例是可调用的,因为该类有一个__call__ 方法,并且当从类实例中调用此实例时,它将运行。是的,我确信这一点。 self 信息未插入,因为在方法调用中附加此信息的语言机制使用类的 __get__ 方法,如下所述。
  • @jsbueno 我明白了。

标签: python decorator python-decorators callable


【解决方案1】:

因此,在 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

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

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)

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

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

【讨论】:

  • 我明白了,添加__get__ 为我修复了它,谢谢您的解释。你会说一种方法比另一种更好吗?
  • 我认为鉴于“复制”方法创建了两种“类型”的装饰器“绑定”和“未绑定”,带有“部分”的方法可能会更好,只是为了避免两个非常相似对象,但性质不同,周围。在性能方面,它们应该是相似的。
  • 另外,请检查我的这个答案以了解更多方法:stackoverflow.com/questions/10294014/… - 在这个答案中,__get__ 没有被使用,因为装饰器在装饰功能之前需要一些参数 - 装饰比发生在__call__,这是@KlausD。对该问题的第一条评论感到困惑。
猜你喜欢
  • 2022-01-22
  • 2023-04-03
  • 2019-09-07
  • 2019-01-24
  • 2021-02-22
  • 1970-01-01
  • 2021-04-07
  • 1970-01-01
  • 2020-09-20
相关资源
最近更新 更多