3

Here's the gist of what I'm trying to do. I have a list of objects, and I know they have an instance method that looks like:

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

I want to (essentialy) decorate these functions at runtime, as I'm iterating over the list of objects. So that their render functions become this:

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

Two caveats:

  1. The render function is part of django. I can't put a decorator inside their library (well I could, but then I have to maintain and migrate this change).
  2. It's an instance method.

An example here: http://wiki.python.org/moin/PythonDecoratorLibrary

Shows how to add a new instance method to a class. The difference here is I want to fall through to the original method after I've memorized that attrs parameter.

Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
Koobz
  • 6,928
  • 6
  • 41
  • 53

2 Answers2

7
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))

You'll probably need some other test to skip methods with a different signature, but, apart from that, decorate_class(whatever) should do what you want on any given class whatever.

Stephan202
  • 59,965
  • 13
  • 127
  • 133
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    You wrote that code from the top of your head, didn't you? :) – Stephan202 Nov 04 '09 at 13:38
  • @Alex: yes, see the revision history. `setattr` was passed `f` instead of `n` as its second argument. – Stephan202 Nov 04 '09 at 15:51
  • 2
    (Recently I found a typo in another code snippet you posted, http://stackoverflow.com/questions/1653500/permutations-of-a-given-set-of-numbers/1653618#1653618. I must say, myself I usually do not feel confident enough to post code without first testing it in the interpreter :) – Stephan202 Nov 04 '09 at 15:56
3

The "classic" way is to subclass. This way you don't have to mess with other peoples classes.

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

The reason for MI is that all classes can inherit from my_render. I like the mixin concept ;-)

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 ]
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • 1
    Interesting that you chose to use MI for a simple example instead of having my_render inherit from someclass. – jamessan Nov 04 '09 at 08:09
  • Yep this is usually how I'd do it, but I'd like to be able to take advantage of decoration simply so I don't have to subclass all of the various types of fields Django offers and MI/decorate on the fly regardless if it's an InputField, DateTimeField, etc. Django's doing a lot of black magic when it renders fields. I've had an adventure figuring out the the fact that each field gets wrapped in a 'BoundField' before it's rendered... – Koobz Nov 05 '09 at 07:11