【问题标题】:Using a class as a decorator? [duplicate]使用类作为装饰器? [复制]
【发布时间】:2021-05-09 19:42:29
【问题描述】:

我认为以下内容可以用作装饰器

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

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)


class A:
    @D
    def f(self, x):
        pass

a=A()
a.f(1)

但我得到TypeError: f() missing 1 required positional argument: 'x'

这是怎么回事,有没有办法像这样使用类作为装饰器?

【问题讨论】:

  • Related: stackoverflow.com/questions/51542063/… 问题是你试图装饰一个实例方法,而不是你用一个类来实现装饰器
  • 确切的问题是self.func(*args, **kwargs) 没有将self 参数传递给self.func(即A.f)。如果你添加self (self.func(self, *args, **kwargs)) 你会遇到另一个问题:这里的self 指的是D 实例,而不是A 实例。事实上,我不确定是否可以使用作为类实现的装饰器来装饰实例方法。你可能需要用一个函数来实现它
  • 其实如果D是一个描述符也是可以的。查看我标记的副本
  • “重复”不是。可能还有其他问题可以解决这个问题,但你标记的问题是关于使用普通函数装饰方法 - 根本没有提到使用类时所需的调整。
  • D,作为一个类,是可调用的,但不是你定义的装饰器。那是D()(除了装饰要用作方法的函数引起的描述符问题)。

标签: python


【解决方案1】:

问题在于,除了装饰器机制之外,还有一种 Python 使用的机制,以便类体内的函数表现为实例方法:它是 "descriptor protocol"。这实际上很简单:所有函数对象都有一个__get__ 方法(但不是__set____del__)方法,它们使它们成为“非数据描述符”。当 Python 从实例中检索属性时,__get__ 以实例作为参数被调用 - __get__ 方法它们必须返回一个可作为方法工作的可调用对象,并且必须知道哪个实例被调用:


# example only - DO NOT DO THIS but for learning purposes,
#  due to concurrency problems:

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

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

    def __get__(self, instance, owner):
        self.instance = instance
        return self

class A:
    @D
    def f(self, x):
        print(self, x)

a=A()
a.f(1)

这将打印“ma​​in.A object at 0x...> 1”

但是,由于很容易察觉,这仅允许在单个实例中一次调用修饰的方法 - 即使拥有多个“A”实例的非并行代码也可能使用错误的实例调用该方法头脑。也就是这个序列:


In [127]: a1 = A()                

In [128]: a2 = A()                

In [129]: f1 =  a1.f              

In [130]: f2 = a2.f               

In [131]: f1() 

最终会调用“a2.f()”而不是“a1.f()”

为避免这种情况,您必须返回来自__get__ 的可调用对象,不需要将实例作为类属性检索。一种方法是创建一个 partial 可调用并包含它 - 但是,请注意,由于这是一个必要的步骤,因此装饰器类本身不需要在__call__ 方法 - 它可以有任何名称:

from functools import partial

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

    def __call__(self, *args, _instance=None, **kwargs):
        if _instance:
            return self.func(_instance, *args, **kwargs)
        else: 
            return self.func(*args, **kwargs)
        

    def __get__(self, instance, owner):
        return partial(self.__call__, _instance=instance)
      

class A:
    @D
    def f(self, x):
        print(self, x)

a=A()
a.f(1)

【讨论】:

    猜你喜欢
    • 2021-07-16
    • 2019-01-01
    • 2017-07-28
    • 2019-02-20
    • 2020-03-26
    • 2020-10-04
    • 2021-11-16
    • 2014-02-18
    • 1970-01-01
    相关资源
    最近更新 更多