【问题标题】:Propagating class decorators to inherited classes将类装饰器传播到继承的类
【发布时间】:2014-01-20 08:51:41
【问题描述】:
import inspect
import functools

def for_all_test_methods(decorator):
    def decorate(cls):
        for name, value in inspect.getmembers(cls, inspect.isroutine):
            if name.startswith('test'):
                setattr(cls, name, test_decorator(getattr(cls, name)))
        return cls
    return decorate

def test_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(func.__name__, args, kwargs)
        res = func(*args, **kwargs)
        return res
    return wrapper

@for_all_test_methods(test_decorator)
class Potato(object):
    def test_method(self):
        print('in method')

class Spud(Potato):
    def test_derived(self):
        print('in derived')

现在,如果我创建一个 spud 实例,它继承的 test_method 仍然是修饰的,但它有一个未修饰的方法 test_derived。不幸的是,如果我也将类装饰器添加到Spud,那么他的test_method 会被装饰两次!

如何正确地将装饰器从父类传播到子类?

【问题讨论】:

    标签: python inheritance decorator python-decorators


    【解决方案1】:

    你不能避免装饰派生类;在子类被装饰后,您可以找到一个类的子类,但不能自动装饰它们。使用元类而不是你需要那种行为。

    您可以做以下两件事之一:

    1. 检测已经装饰的方法;如果有 __wrapped__ 属性,则您有一个包装器:

      def for_all_test_methods(decorator):
          def decorate(cls):
              for name, value in inspect.getmembers(cls, inspect.isroutine):
                  if name.startswith('test') and not hasattr(value, '__wrapped__'):
                      setattr(cls, name, test_decorator(getattr(cls, name)))
              return cls
          return decorate
      
    2. 将类装饰器限制为仅直接方法:

      def for_all_test_methods(decorator):
          def decorate(cls):
              for name, value in cls.__dict__.iteritems():
                  if name.startswith('test') and inspect.isroutine(value)):
                      setattr(cls, name, test_decorator(getattr(cls, name)))
              return cls
          return decorate
      

    【讨论】:

    • 先生,这是一个很好的答案。
    • 1. __wrapped__ 不能已经被其他装饰器设置了吗? 2. 那我还要装饰派生类吗?
    • @wim:是的,__wrapped__ 可以由另一个装饰器设置。您当然必须装饰派生类。
    【解决方案2】:

    您可以通过以下方式通过使用元类而不是装饰类来实现此目的:

    import inspect
    import functools
    
    def test_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(func.__name__, args, kwargs)
            res = func(*args, **kwargs)
            return res
        return wrapper
    
    def make_test_deco_type(decorator):
        class TestDecoType(type):
            def __new__(cls, clsname, bases, dct):
                for name, value in dct.items():
                    if name.startswith('test') and inspect.isroutine(value):
                        dct[name] = decorator(value)
                return super().__new__(cls, clsname, bases, dct)
        return TestDecoType
    
    class Potato(object, metaclass=make_test_deco_type(test_decorator)):
        def test_method(self):
            print('in method')
    
    class Spud(Potato):
        def test_derived(self):
            print('in derived')
    

    在 Python 2.x 上,您将使用 __metaclass__ = make_test_deco_type(test_decorator) 作为类主体的第一行,而不是使用类语句的 metaclass=... 部分。您还需要将super() 替换为super(TestDecoType, cls)

    【讨论】:

    • 这看起来比我往下走的路线要好。你如何处理python2上的super行?
    • 但是当时还没有定义self。我使用super(TestDecoType, cls)吗?
    猜你喜欢
    • 2011-03-23
    • 2023-04-09
    • 2011-11-20
    • 2018-10-26
    • 2012-11-14
    • 2016-12-05
    • 2019-12-07
    • 2015-02-07
    • 2018-06-27
    相关资源
    最近更新 更多