动态分配 sub class 依赖装饰器

Dynamically assigning sub class dependent decorators

我有一个 class 有一个基本方法,还有一个子 class 有相同的基本功能,但有额外的行为,可以用装饰器实现。

class cls_with_basic_method:

      #if...exec("@decoratorA")
      #if...exec("@decoratorB")
      #...
      def basic_method(arg):
      #...
         return arg

class cls_with_basic_method_and_decoratorA(class_with_basic_method):
      #...

class cls_with_basic_method_and_decoratorB(class_with_basic_method):
      #...

#...

似乎最快的解决方案是,如果我能够在调用 subclass 方法时执行特定的装饰器,但想不出在 python 中表达它的方式.这可以轻松完成吗?

修饰的函数或方法通常是不同于它修饰的函数或方法的对象 [*] - 因此,您可以直接以显式方式包装原始的 class' 方法。这相当简单,而且相当无聊 - 但如果您只需要装饰子 classes:

的几个方法,它就会起作用
class cls_with_basic_method:

      def basic_method(arg):
         #...
         return arg

class cls_with_basic_method_and_decoratorA(class_with_basic_method):
      basic_method = decoratorA(cls_with_basic_method.basic_method)


class cls_with_basic_method_and_decoratorB(class_with_basic_method):
      basic_method = decoratorB(cls_with_basic_method.basic_method)

唯一特别的地方是使用具有常规函数调用语法的装饰器,而不是使用“@...”语法 - 这样它们就可以在表达式中使用。

这种方法更无聊,因为你必须在每次修饰时在 class 主体中硬编码 superclass 名称,因为你不能使用 super class 正文,仅来自内部方法。

[*] 虽然一些装饰器只是将元数据添加到它们装饰的可调用对象和 return 对象本身 - 这种方法不适用于此类装饰器,因为它们会影响 superclass 还有。

现在,进一步解决您的问题 - 您想要的只是在 superclass 上调用 subclass 上的任意方法时将它们包装起来。如果您重写 class__getattribute__,这可以或多或少地自动完成 - 然后您可以创建一个 class 层次结构,其中包含一个特殊的 "decorator" 属性,每个方法都会调用该属性调用 - 或多或少是这样的:

class cls_with_basic_method:
    _auto_decorate = set(("basic_method", ...))

    _decorator = lambda x: x  # NOP decorator
    def basic_method(arg):
        #...
        return arg

    def __getattribute__(self, attrname):
        attr = object.__getattribute__(self, attr)
        # shortcircuit non-method retrievelas as fast as possible:
        if  not attrname in __class__._auto_decorate not callable(attr):
            return attr
        return self.__class__._decorator(attr) 


class cls_with_basic_method_and_decoratorA(class_with_basic_method):
    _decorator = decoratorA

class cls_with_basic_method_and_decoratorB(class_with_basic_method):
    _decorator = decoratorB

当然,如果您需要为不同的方法使用不同的装饰器,只需相应地更改 __getattribute__ 中的代码 - 最简单的方法是使 _decorator 属性成为字典而不是指向一个简单的功能。

(附带说明:__class__ 魔法变量在方法内部使用时,是一个 Python 3 事物:它自动包含对 class 的引用定义于(在本例中,cls_with_basic_method)。

这种方法会在每次调用时重新装饰方法 - 它的开销并不像看起来那么多 - Python 的默认方法检索机制本身也同样复杂 - 但如果你更喜欢装饰class 创建的方法相反,您可以在 metaclass 中使用类似的机制,而不是依赖 __getattribute__.

from itertools import chain

class AutoDecorate(type):
    def __new__(metacls, name, bases, dct):
        if "_decorator" not in dct:
            dct["_decorator"] = lambda x: x # NOP decorator
        all_bases = list(chain(base.__mro__ for base in bases)) 
        for base in all_bases:
            if not "_auto_decorate" in base.__dict__:
                continue
            for method_name in base.auto_decorate:
                if method_name not in dct:
                    dct[method_name] = dct["_decorator"](getattr(base, method_name))
        return super().__new__(name, bases, dct)

class cls_with_basic_method(metaclass=AutoDecorate):
    _auto_decorate = set(("basic_method", ...))

    def basic_method(arg):
        #...
        return arg

class cls_with_basic_method_and_decoratorA(class_with_basic_method):
    _decorator = decoratorA

class cls_with_basic_method_and_decoratorB(class_with_basic_method):
    _decorator = decoratorB

这实际上比看起来更简单:在层次结构上创建新的 class 后,它只搜索所有超级 class 具有 _auto_decorate 属性的元素 -然后它获取该列表中的方法,并用正在创建的 class 的 _decorator 属性中的装饰器装饰它们。

根据您的要求,我会说您正在处理一个需要“aspect oriented programing”方法的项目。有几个 Python 库可以提供该功能 - 也许您应该看一看。如果您这么认为,请搜索可以提供适当 Python 面向方面功能的模块并使用它们。