10

Вопрос по python – Автоматически украшать каждый метод экземпляра в классе

Я хочу применить один и тот же декоратор к каждому методу в данном классе, кроме тех, которые начинаются и заканчиваются на__.

Мне кажется, это должно быть выполнимо, используя декоратор класса. Есть ли подводные камни, о которых следует знать?

В идеале я также хотел бы иметь возможность:

  1. disable this mechanism for some methods by marking them with a special decorator
  2. enable this mechanism for subclasses as well
  3. enable this mechanism even for methods that are added to this class in runtime

[Примечание: я использую Python 3.2, поэтому я в порядке, если это зависит от недавно добавленных функций.]

Вот моя попытка:

_methods_to_skip = {}

def apply(decorator):
  def apply_decorator(cls):
    for method_name, method in get_all_instance_methods(cls):
      if (cls, method) in _methods_to_skip:
        continue
      if method_name[:2] == `__` and method_name[-2:] == `__`:
        continue
      cls.method_name = decorator(method)
  return apply_decorator

def dont_decorate(method):
  _methods_to_skip.add((get_class_from_method(method), method))
  return method

Вот вещи, с которыми у меня проблемы:

  • how to implement get_all_instance_methods function
  • not sure if my cls.method_name = decorator(method) line is correct
  • how to do the same to any methods added to a class in runtime
  • how to apply this to subclasses
  • how to implement get_class_from_method
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded__getattr__Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded__metaclass__Error: User Rate Limit Exceededclass Foo(object, metaclass=decorate_all(printer))Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • @anonymous downvoter: пожалуйста, будьте вежливы и прокомментируйте, что вам не нравится при понижении голосов. Является ли мой вопрос настолько плохим, что я даже не заслуживаю объяснения того, что с ним не так?

    от max
  • Ненавижу это говорить, но отрицательный голос был от меня, и я изложил свои причины. Теперь вы уже дали свою реализацию, и вопрос более конкретный, я его сниму.

    от Gareth Latty
  • Похоже, у вас есть четкое представление о том, как это сделать, вы просите людей реализовать это для вас? Если нет, то почему бы не опубликовать это после попытки, если у вас есть реальная проблема?

    от Gareth Latty
  • @ Lattyware Спасибо за указание на это; Я обновил вопрос, чтобы показать, в чем моя проблема.

    от max
  • Ах, прости, прости. Полностью согласен с вашим downvote тогда. Надеюсь, обновленный вопрос в порядке.

    от max
  • 14

    Я думаю

    что это лучше сделать с метаклассом, чтобы обрабатывать как методы выполнения, так и методы подкласса. Я не вижу элегантного способа автоматически обрабатывать подклассы с помощью декоратора классов.

    from types import FunctionType
    
    # check if an object should be decorated
    def do_decorate(attr, value):
        return ('__' not in attr and
                isinstance(value, FunctionType) and
                getattr(value, 'decorate', True))
    
    # decorate all instance methods (unless excluded) with the same decorator
    def decorate_all(decorator):
        class DecorateAll(type):
            def __new__(cls, name, bases, dct):
                for attr, value in dct.iteritems():
                    if do_decorate(attr, value):
                        dct[attr] = decorator(value)
                return super(DecorateAll, cls).__new__(cls, name, bases, dct)
            def __setattr__(self, attr, value):
                if do_decorate(attr, value):
                    value = decorator(value)
                super(DecorateAll, self).__setattr__(attr, value)
        return DecorateAll
    
    # decorator to exclude methods
    def dont_decorate(f):
        f.decorate = False
        return f
    

    И пример его использования (Python 2, но тривиально модифицированный для Python 3):

    def printer(f):
        print f
        return f
    
    class Foo(object):
        __metaclass__ = decorate_all(printer)
        def bar(self):
            pass
        @dont_decorate
        def baz(self):
            pass
        @classmethod
        def test(self):
            pass
    # prints
    # <function bar at 0x04EB59B0>
    
    class AnotherName(Foo):
        def blah(self):
            pass
    # prints
    # <function blah at 0x04EB5930>
    
    Foo.qux = lambda: 1
    # prints
    # <function <lambda> at 0x04EB57F0>
    

  • 0

    Вы можете сделать это (не уверен, что это самый элегантный способ):

    def get_all_instance_methods(x):
        return filter(callable, map(lambda d: getattr(x, d), dir(x)))
    

    Дляcls.method_name, вам придется использоватьgetattr(cls, method_name).