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

时间:2019-03-17 13:56:47

标签: python python-3.x python-decorators metaclass class-method

我有以下Python元类,它们为每个类添加了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函数中访问decorator类(或其他任何类)?

我尝试在@classmethod函数上使用deco_with_args时没有运气。

3 个答案:

答案 0 :(得分:1)

259999999999999786837179271969944对装饰器没有任何帮助,因为它不是通过类或实例调用的。 @classmethoddescriptor,并且描述符仅对属性访问有效。换句话说,仅当装饰器的调用方式为classmethod时,它才会有所帮助。

下一个问题是,在执行装饰器时,类还不存在。在创建类之前,Python执行 函数体中的所有代码。因此,无法访问@Bar.deco_with_args('baz')deco_with_args中的类。

答案 1 :(得分:1)

您的问题有两种解释-调用示例中名为cls的函数时,是否需要decorator可用(即,需要装饰的方法才能成为类方法),它会本身就可以转换为类方法:

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

第二个是如果您需要cls在调用deco_with_args本身时,在创建修饰函数本身时,在类创建时可用。现在被列为已接受的答案列出了以下直接问题:运行类主体时该类尚不存在,因此,无法在分析该类结束时正文,您可以拥有类本身已知的方法。

但是,不同于该答案试图暗示的是,这不是真正的交易。您要做的就是在类创建过程结束时懒惰地运行装饰器代码(需要cls的代码)。您已经有一个元类设置,因此,只需在装饰代码周围添加另一个可调用层,就可以做到这一点几乎是简单的:

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之后的任何其他Python语句之前运行。 如果您的装饰器会影响在类主体本身内部执行的其他元素,那么您所需要做的就是将它们包装起来以保证也可以进行延迟执行。

答案 2 :(得分:0)

您可以使用descriptor protocol捕获对方法的调用,并动态添加类作为参数:

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

此处的主要“技巧”是,调用Bar.some_function()时的协议是先调用__get__,然后依次调用__call__返回的函数__get__

请注意,__get__也会在您执行Bar.some_function时被调用,这就是@property这样的修饰符所使用的。

一句话,在使用classmethod时,您不应该为您的第一个参数self命名,因为它很容易混淆(这会使人们认为第一个参数是实例而不是类对象/类型)。