2014-01-02 2 views
4
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') 

Теперь, если я создаю окучивать экземпляр test_method, который он унаследовал остатки оформленных, но у него есть непараметризованный метод test_derived. К сожалению, если я добавлю декоратор класса на Spud, тогда его test_method получит украшенное дважды!распространяющегося класс декораторы к унаследованным классам

Как правильно распределять декораторы из родительского класса на детей?

ответ

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) в качестве первой строки тела класса вместо того, чтобы иметь часть инструкции класса. Вам также необходимо будет заменить super() на super(TestDecoType, cls).

+0

Это выглядит лучше, чем маршрут, по которому я направлялся. Как вы обрабатываете строку 'super' на python2? – wim

+0

@wim См. Мое редактирование, вы будете использовать 'super (TestDecoType, self)'. –

+0

Но сам не определяется в то время. Использую ли я 'super (TestDecoType, cls)'? – wim

2

Вы не можете избежать украшения производных классов; вы можете найти подклассы класса после того, как подклассы были украшены, но не авто-украсить их. Используйте метакласс вместо того, чтобы вам нужно подобное поведение.

Вы можете сделать одну из двух вещей:

  1. Detect уже оформленных метод; если есть атрибут у вас есть обертку __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. Limit класс декоратора для прямых методов только:

    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 
    
+0

Это, сэр, является отличным ответом. – Hyperboreus

+0

1. Не могли ли '__wrapped__' уже установить какой-либо другой декоратор? 2. Тогда мне нужно будет также украсить производный класс? – wim

+0

@wim: Да, '__wrapped__' мог быть установлен другим декоратором. Вы, конечно, ** должны украшать производные классы. –