Декорирование методов экземпляра в Python

Вот суть того, что я пытаюсь сделать. У меня есть список объектов, и я знаю, что у них есть метод экземпляра, который выглядит так:

def render(self, name, value, attrs)
   # Renders a widget...

Я хочу (по существу) украсить эти функции во время выполнения, поскольку я перебираю список объектов. Чтобы их функции рендеринга стали такими:

def render(self, name, value, attrs)
   self.attrs=attrs
   # Renders a widget...

Два предостережения:

  1. Функция рендеринга является частью django. Я не могу поместить декоратор в их библиотеку (ну, я мог бы, но тогда я должен поддерживать и переносить это изменение).
  2. Это метод экземпляра.

Пример здесь: http://wiki.python.org/moin/PythonDecoratorLibrary

Показывает, как добавить новый метод экземпляра в класс. Разница здесь в том, что я хочу перейти к исходному методу после того, как запомню этот параметр attrs.


person Koobz    schedule 04.11.2009    source источник


Ответы (2)


def decorate_method(f):
  def wrapper(self, name, value, attrs):
    self.attrs = attrs
    return f(self, name, value, attrs)
  return wrapper

def decorate_class(c):
  for n in dir(c):
    f = getattr(c, n)
    if hasattr(f, 'im_func'):
      setattr(c, n, decorate_method(f.im_func))

Вам, вероятно, понадобится какой-то другой тест, чтобы пропустить методы с другой сигнатурой, но, помимо этого, decorate_class(whatever) должен делать то, что вы хотите, для любого заданного класса whatever.

person Alex Martelli    schedule 04.11.2009
comment
Вы написали этот код из головы, не так ли? :) - person Stephan202; 04.11.2009
comment
@Alex: да, посмотрите историю изменений. setattr было передано f вместо n в качестве второго аргумента. - person Stephan202; 04.11.2009
comment
(Недавно я обнаружил опечатку в другом опубликованном вами фрагменте кода, stackoverflow.com/questions/1653500/. Должен сказать, что сам я обычно не чувствую себя достаточно уверенно, чтобы публиковать код без предварительного тестирования его в интерпретаторе :) - person Stephan202; 04.11.2009

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

class someclass(object):
    def render(self, name, value, attrs):
        print hasattr(self, 'attrs')

class my_render(object):
    def render(self, name, value, attrs):
        self.attrs = attrs # kind of decorating the function here
        return super(my_render, self).render(name, value, attrs)

class my_class(my_render, someclass): 
    pass    

someclass().render(1,2,3) # -> False
my_class().render(1,2,3) # -> True

Причина MI в том, что все классы могут наследовать от my_render. Мне нравится концепция миксина ;-)

class my_otherclass(my_render, someotherclass): pass
class my_thirdclass(my_render, thirdclass): pass

# or less explicit
classlist = [ someclass, someotherclass ]
newclasses = [ type('my_'+cls.__name__, (my_render,cls), {}) for cls in classlist ]
person Jochen Ritzel    schedule 04.11.2009
comment
Интересно, что вы решили использовать MI для простого примера вместо того, чтобы my_render наследовал какой-то класс. - person jamessan; 04.11.2009
comment
Да, обычно я так и делаю, но я хотел бы иметь возможность воспользоваться преимуществами декорирования просто, чтобы мне не приходилось создавать подклассы всех различных типов полей, которые предлагает Django, и MI/decorate на лету независимо от этого. если это InputField, DateTimeField и т. д. Django делает много черной магии, когда отображает поля. У меня было приключение, когда я выяснил тот факт, что каждое поле оборачивается в «BoundField» до того, как оно будет отображено... - person Koobz; 05.11.2009