Автоматическое украшение каждого метода экземпляра в классе

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

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

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

  1. отключить этот механизм для некоторых методов, пометив их специальным декоратором
  2. включить этот механизм и для подклассов
  3. включить этот механизм даже для методов, которые добавляются к этому классу во время выполнения

[Примечание: я использую 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

Вот с чем у меня проблемы:

  • как реализовать функцию get_all_instance_methods
  • не уверен, что моя строка cls.method_name = decorator(method) верна
  • как сделать то же самое для любых методов, добавленных в класс во время выполнения
  • как применить это к подклассам
  • как реализовать get_class_from_method

person max    schedule 08.04.2012    source источник
comment
Похоже, у вас есть твердое представление о том, как это сделать, вы просите людей реализовать это для вас? Если нет, то почему бы не опубликовать это после того, как попробуете, если у вас есть реальная проблема?   -  person Gareth Latty    schedule 09.04.2012
comment
@Lattyware Спасибо, что указали на это; Я обновил вопрос, чтобы показать, в чем моя проблема.   -  person max    schedule 09.04.2012
comment
@anonymous downvoter: пожалуйста, будьте вежливы и прокомментируйте, что вам не нравится при голосовании против. Неужели мой вопрос настолько плох, что я даже не заслуживаю объяснения, что с ним не так?   -  person max    schedule 09.04.2012
comment
Э-э, ненавижу это говорить, но отрицательный голос был от меня, и я привел свои причины. Теперь вы дали свою реализацию до сих пор, и вопрос более конкретен, я удалю его.   -  person Gareth Latty    schedule 09.04.2012
comment
Ах, извините, извините. Тогда полностью согласен с вашим минусом. Надеюсь, обновленный вопрос в порядке.   -  person max    schedule 09.04.2012
comment
Не удалил бы мой отрицательный голос, если бы это не было XD.   -  person Gareth Latty    schedule 09.04.2012
comment
Вот кое-что, с чего можно начать get_all_attributes: for f in dir(cls): print(f, type(cls.__getattribute__(f)))   -  person alexis    schedule 09.04.2012


Ответы (2)


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

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>
person agf    schedule 08.04.2012
comment
Хорошо сделано. Я почти закончил с аналогичным подходом (но вызывая декоратор для каждого __getattr__ вместо предварительного создания словаря), но в этом нет смысла: ваш более тщательный. - person ephemient; 09.04.2012
comment
Примечание: чтобы этот код работал для Python 3, удалите назначение __metaclass__ и вместо этого используйте class Foo(object, metaclass=decorate_all(printer)). Я застрял на этом на несколько минут, так что, надеюсь, это поможет кому-то еще. - person Alexander; 06.04.2014

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

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

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

person Gustav Larsson    schedule 08.04.2012
comment
Это относится не только к методам экземпляра, но и к любым вызываемым объектам. Он также не работает автоматически с подклассами, методами, добавленными во время выполнения, и не допускает исключения. Так что это не соответствует большинству его требований - person agf; 09.04.2012
comment
@agf Вы правы, ваше решение для метакласса намного лучше. Мое решение - скорее уродливый хак (если кому-то это нравится). - person Gustav Larsson; 09.04.2012