Python - Metaclass装饰器 - 如何使用@classmethod

问题描述 投票:0回答:3

我有以下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没有运气。

python python-3.x python-decorators metaclass class-method
3个回答
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语句之前运行。如果你的装饰器会影响在类体内部执行的其他元素,那么你需要做的就是将它们包装起来以保证延迟执行。


1
投票

@classmethod对你的装饰器没什么用处,因为它不是通过类或实例调用的。 classmethod是一个descriptor,描述符只对属性访问有效。换句话说,只有装饰器像@Bar.deco_with_args('baz')一样被调用时才有用。

下一个问题是在执行装饰器时类仍然不存在。 Python在创建类之前执行函数体中的所有代码。因此,无法访问deco_with_argsdecorator中的课程。


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因为它令人困惑(它会让人们认为第一个参数是实例而不是类对象/类型)。

© www.soinside.com 2019 - 2024. All rights reserved.