Python - 元类装饰器 - 如何使用@classmethod

Python - Metaclass decorator - How to use @classmethod

我有以下 Python metaclass,它为每个 class 添加了一个 deco_with_args 装饰器:

def deco_with_args(baz):
    def decorator(func):
        ...
        return func
    return decorator

class Foo(type):
    def __prepare__(name, bases):    
        return {'deco_with_args': deco_with_args}

这让我可以像这样使用装饰器:

class Bar(metaclass=Foo):
    @deco_with_args('baz')
    def some_function(self):
        ...

如何使 deco_with_args 装饰器的行为类似于 @classmethod,以便我可以访问 Bar class(或任何其他 class)来自 decorator 函数?

我曾尝试在 deco_with_args 函数上使用 @classmethod,但没有成功。

您可以使用 descriptor protocol 捕获对方法的调用并即时添加 class 作为参数:

def another_classmethod(baz):

  class decorator:
    def __init__(self, func):
      self.func = func
    def __get__(self, instance, owner):
      def new_call(*args, **kwargs):
        print(baz, self.func(owner, *args, **kwargs))
      return new_call

  return decorator


class Bar():
    @another_classmethod('baz')
    def some_function(cls):
        return f"test {cls.__name__}"

Bar.some_function()

这会打印:

baz test Bar

这里的主要"trick"是调用Bar.some_function()时的协议是在__get__返回的函数上先调用__get__然后__call__

请注意,当您执行 Bar.some_function 时也会调用 __get__,这就是 @property 等装饰器中使用的内容。

注意一点,当使用 class 方法时,您不应该将第一个参数命名为 self,因为这会造成混淆(这会让人们认为第一个参数是一个实例而不是一个 class object/type).

@classmethod 对您的装饰器没有任何用处,因为它不是通过 class 或实例调用的。 classmethod是一个descriptor,描述符只对属性访问生效。换句话说,只有像 @Bar.deco_with_args('baz').

这样调用装饰器才会有帮助

下一个问题是class 在装饰器执行时还不存在。 Python 在 创建 class 之前执行函数体 中的所有代码。所以无法访问 deco_with_argsdecorator.

中的 class

你的问题有两种解释——如果你需要 cls 在你的例子中调用名为 decorator 的函数时可用(即你需要你的装饰方法成为 class methods), 自身转化为classmethod:

即可
def deco_with_args(baz):
    def decorator(func):
        ...
        return classmethod(func)
    return decorator

第二个是如果您需要 clsdeco_with_args 本身被调用时可用,当创建修饰函数本身时,在 class 创建时。现在被列为已接受的答案列出了一个直接的问题:当 class 正文为 运行 时,class 尚不存在,因此,在解析 class 正文结束时,您无法使用已知 class 本身的方法。

但是,与该答案试图暗示的不同,这不是真正的交易。您所要做的就是 运行 您的装饰器代码(需要 cls 的代码)在 class 创建过程结束时懒惰地进行。你已经有了一个 metaclass 设置,所以这样做几乎是微不足道的,只需在你的装饰器代码周围添加另一个可调用层:

def deco_with_args(baz):
    def outter_decorator(func):
        def decorator(cls):
            # Code that needs cls at class creation time goes here
            ...

            return func
        return decorator
    outter_decorator._deco_with_args = True
    return outter_decorator

class Foo(type):
    def __prepare__(name, bases):    
        return {'deco_with_args': deco_with_args}

    def __init__(cls, cls_name, bases, namespace, **kwds):
        for name, method in cls.__dict__.items():
            if getattr(method, '_deco_with_args', False):
                cls.__dict__[name] = method(cls)

        super().__init__(cls_name, bases, namespace, **kwds)

这将是 运行,当然,在 class 主体执行完成之后,但在 class 之后的任何其他 Python 语句之前 运行. 如果您的装饰器会影响在 class 主体内部执行的其他元素,您需要做的就是将它们包裹起来以保证延迟执行。