将类装饰器传播到继承的类

时间:2014-01-02 20:03:56

标签: python inheritance decorator python-decorators

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会被装饰两次!

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

2 个答案:

答案 0 :(得分:2)

你无法避免装饰派生类;您可以在子类装饰后找到类的子类,但不能自动装饰它们。使用元类代替您需要这种行为。

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

  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 :(得分:1)

以下是使用元类而不是装饰类来实现此目的的方法:

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)