【问题标题】:Dynamically assigning sub class dependent decorators动态分配子类依赖的装饰器
【发布时间】:2015-05-20 12:16:59
【问题描述】:

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

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):
      #...

#...

似乎最快的解决方案是如果我能够在调用子类方法时执行特定的装饰器,但想不出用 python 表达它的方法。这很容易做到吗?

【问题讨论】:

    标签: python-3.x dynamic decorator


    【解决方案1】:

    修饰的函数或方法通常与它修饰的函数或方法是不同的对象 [*] - 因此,您可以以显式方式包装原始类的方法。这很简单,也很无聊——但如果你只需要装饰子类的几个方法,它就会起作用:

    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)
    

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

    此方法更无聊,因为您必须在每次修饰时在类主体中硬编码超类名称,因为您不能从类主体中使用super,而只能从内部方法中使用。

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

    现在,进一步解决您的问题 - 您想要的只是在子类上调用任意方法时在超类上包装任意方法。如果您覆盖 class__getattribute__,这或多或少可以自动完成 - 然后您可以创建一个具有特殊“装饰器”属性的类层次结构,每个方法调用都会调用该属性 - 或多或少像这样:

    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 的东西:它自动包含对其定义的类的引用(在本例中为 cls_with_basic_method)。

    这种方法将在每次调用时重新装饰方法 - 它不像看起来那样开销很大 - Python 的默认方法检索机制本身同样复杂 - 但如果您更喜欢在类创建时装饰方法,请使用可以在元类中使用类似的机制,而不是依赖__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
    

    这实际上比看起来简单:在层次结构上创建一个新类时,它只搜索所有超类以查找具有_auto_decorate 属性的那些 - 然后它获取该列表中的方法,并用正在创建的类的_decorator 属性中的装饰器。

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

    【讨论】:

      猜你喜欢
      • 2013-11-22
      • 2017-10-12
      • 1970-01-01
      • 1970-01-01
      • 2021-01-05
      • 2013-04-08
      • 2021-10-14
      • 2011-06-18
      • 2013-04-04
      相关资源
      最近更新 更多