views:

46

answers:

1

This doesn't work:

def register_method(name=None):
    def decorator(method):
        # The next line assumes the decorated method is bound (which of course it isn't at this point)
        cls = method.im_class
        cls.my_attr = 'FOO BAR'
        def wrapper(*args, **kwargs):
            method(*args, **kwargs)
        return wrapper
    return decorator

Decorators are like the movie Inception; the more levels in you go, the more confusing they are. I'm trying to access the class that defines a method (at definition time) so that I can set an attribute (or alter an attribute) of the class.

Version 2 also doesn't work:

def register_method(name=None):
    def decorator(method):
        # The next line assumes the decorated method is bound (of course it isn't bound at this point).
        cls = method.__class__  # I don't really understand this.
        cls.my_attr = 'FOO BAR'
        def wrapper(*args, **kwargs):
            method(*args, **kwargs)
        return wrapper
    return decorator

The point of putting my broken code above when I already know why it's broken is that it conveys what I'm trying to do.

+2  A: 

I don't think you can do what you want to do with a decorator (quick edit: with a decorator of the method, anyway). The decorator gets called when the method gets constructed, which is before the class is constructed. The reason your code isn't working is because the class doesn't exist when the decorator is called.

jldupont's comment is the way to go: if you want to set an attribute of the class, you should either decorate the class or use a metaclass.

EDIT: okay, having seen your comment, I can think of a two-part solution that might work for you. Use a decorator of the method to set an attribute of the method, and then use a metaclass to search for methods with that attribute and set the appropriate attribute of the class:

def TaggingDecorator(method):
  "Decorate the method with an attribute to let the metaclass know it's there."
  method.my_attr = 'FOO BAR'
  return method # No need for a wrapper, we haven't changed
                # what method actually does; your mileage may vary

class TaggingMetaclass(type):
  "Metaclass to check for tags from TaggingDecorator and add them to the class."
  def __new__(cls, name, bases, dct):
    # Check for tagged members
    has_tag = False
    for member in dct.itervalues():
      if hasattr(member, 'my_attr'):
        has_tag = True
        break
    if has_tag:
      # Set the class attribute
      dct['my_attr'] = 'FOO BAR'
    # Now let 'type' actually allocate the class object and go on with life
    return type.__new__(cls, name, bases, dct)

That's it. Use as follows:

class Foo(object):
  __metaclass__ = TaggingMetaclass
  pass

class Baz(Foo):
  "It's enough for a base class to have the right metaclass"
  @TaggingDecorator
  def Bar(self):
    pass

>> Baz.my_attr
'FOO BAR'

Honestly, though? Use the supported_methods = [...] approach. Metaclasses are cool, but people who have to maintain your code after you will probably hate you.

Peter Milley
@Peter - thanks. I'll start regrowing the hair I've lost in the last hour now :) How would I go about doing that though? I don't understand meta classes, but I also don't understand how decorating the class could help with what I'm doing. To clarify, I need to be able to run a method on instances of the class `self.supports_method(method_name_string)` to see if the methods are supported. I'm trying to make it "cool" though without sub-classers having to declare a `supported_methods = ['method_one', 'method_two']` attribute on each class.
orokusaki
@orokusaki: see the edit.
Peter Milley
@Peter - excellent - I just spent the last 30 minutes reading on IBM and other SO questions about meta classes and I'm glad I did. I came to the conclusion (based on the use of `func.is_hook` in an SO answer) that I would need to mark the methods for inclusion in my decorator (unless I wanted to trust pure meta-magic to figure out what I wanted through other conventions). I was about to hang myself after I realized how much more "figuring out" I had left for the day. That's what I refreshed the page :) You just saved the rest of my hair. Thanks Peter.
orokusaki
@Peter - I might trust your wisdom about not using Meta programming for this, but not until I do it first :) (I'll forget how if I don't).
orokusaki