Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - decorator - trying to access the parent class of a method

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.

like image 532
orokusaki Avatar asked Dec 29 '22 06:12

orokusaki


1 Answers

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.

like image 159
Peter Milley Avatar answered Apr 13 '23 00:04

Peter Milley