开发者

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

开发者 https://www.devze.com 2023-01-18 23:23 出处:网络
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)

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.
   开发者_如何学JAVA     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.


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.


Rather than use a metaclass, in python 2.6+ you should use a class decorator. You can wrap the function and class decorators up as methods of a class, like this real-world example.

I use this example with djcelery; the important aspects for this problem are the "task" method and the line "args, kw = self.marked[klass.dict[attr]]" which implicitly checks for "klass.dict[attr] in self.marked". If you want to use @methodtasks.task instead of @methodtasks.task() as a decorator, you could remove the nested def and use a set instead of a dict for self.marked. The use of self.marked, instead of setting a marking attribute on the function as the other answer did, allows this to work for classmethods and staticmethods which, because they use slots, won't allow setting arbitrary attributes. The downside of doing it this way is that the function decorator MUST go above other decorators, and the class decorator MUST go below, so that the functions are not modified / re=wrapped between one and the other.

class DummyClass(object):
    """Just a holder for attributes."""
    pass

class MethodTasksHolder(object):
    """Register tasks with class AND method decorators, then use as a dispatcher, like so:

    methodtasks = MethodTasksHolder()

    @methodtasks.serve_tasks
    class C:
        @methodtasks.task()
        #@other_decorators_come_below
        def some_task(self, *args):
            pass

        @methodtasks.task()
        @classmethod
        def classmethod_task(self, *args):
            pass

        def not_a_task(self):
            pass

    #..later
    methodtasks.C.some_task.delay(c_instance,*args) #always treat as unbound
        #analagous to c_instance.some_task(*args) (or C.some_task(c_instance,*args))
    #...
    methodtasks.C.classmethod_task.delay(C,*args) #treat as unbound classmethod!
        #analagous to C.classmethod_task(*args)
    """ 
    def __init__(self):
        self.marked = {}

    def task(self, *args, **kw):
        def mark(fun):
            self.marked[fun] = (args,kw)
            return fun
        return mark

    def serve_tasks(self, klass):
        setattr(self, klass.__name__, DummyClass())
        for attr in klass.__dict__:
            try:
                args, kw = self.marked[klass.__dict__[attr]]
                setattr(getattr(self, klass.__name__), attr, task(*args,**kw)(getattr(klass, attr)))
            except KeyError:
                pass
        #reset for next class
        self.marked = {}
        return klass
0

精彩评论

暂无评论...
验证码 换一张
取 消