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

This does not 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 you have, the more they get confused. I am trying to access a class that defines a method (during definition) so that I can set the attribute (or change the attribute) of the class.

Version 2 also does not 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 installation point of my broken code above, when I already know why it is broken, is that it conveys what I'm trying to do.

+4
source share
2 answers

I don’t think that you can do what you want to do with the decorator (quick edit: using the method decorator, anyway). The decorator is called when the method is built, which is before the class is created. The reason your code does not work is because the class does not exist when the decorator is called.

Jldupont comment is the way to go: if you want to set a class attribute, you must either decorate the class or use a metaclass.

EDIT: Ok, after seeing your comment, I can think of a two-part solution that might work for you. Use the method decorator to set the method attribute, and then use the metaclass to search for methods with this attribute and set the corresponding class attribute:

 def TaggingDecorator(method): "Decorate the method with an attribute to let the metaclass know it 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) 

What is it. Use the following:

 class Foo(object): __metaclass__ = TaggingMetaclass pass class Baz(Foo): "It enough for a base class to have the right metaclass" @TaggingDecorator def Bar(self): pass >> Baz.my_attr 'FOO BAR' 

Honestly, right? Use the supported_methods = [...] approach. The metaclasses are cool, but the people who need to support your code after you probably hate you.

+7
source

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

I use this example with djcelery; important aspects of this problem are the "tasks" method and the string "args, kw = self.marked [klass. dict [attr]]", which implicitly checks the "class. dict [attr] in self.marked". If you want to use @ methodtasks.task instead of @ methodtasks.task () as a decorator, you can remove the nested def and use the set instead of dict for self.marked. Using self.marked, instead of setting the marking attribute in the function as another answer, allows this to work for classmethods and staticmethods, which, since they use slots, will not allow the setting of arbitrary attributes. The disadvantage of this method is that the function decorator MUST go higher than other decorators, and the class decorator MUST go lower, so the functions do not change / re = are 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 
+2
source

All Articles