views:

415

answers:

4

Hi I have something roughly like the following. Basically I need to access the class of an instance method from a decorator used upon the instance method in its definition.

def decorator(view):
    # do something that requires view's class
    print view.im_class
    return view

class ModelA(object):
    @decorator
    def a_method(self):
        # do some stuff
        pass

The code as-is gives

AttributeError: 'function' object has no attribute 'im_class'

I found similar question/answers 1 2 but these rely upon a workaround that grabs the instance at run-time by snatching the first parameter. In my case I will be calling the method based upon the information gleaned from its class, so I can't wait for a call to come in.

Thank you.

A: 

You will have access to the class of the object on which the method is being called in the decorated method that your decorator should return. Like so:

def decorator(method):
    # do something that requires view's class
    def decorated(self, *args, **kwargs):
        print 'My class is %s' % self.__class__
        method(self, *args, **kwargs)
    return decorated

Using your ModelA class, here is what this does:

>>> obj = ModelA()
>>> obj.a_method()
My class is <class '__main__.ModelA'>
Will McCutchen
Thanks but this is exactly the solution I referenced in my question that doesn't work for me. I am trying to implement an observer pattern using decorators and I will never be able to call the method in the correct context from my observation dispatcher if I don't have the class at some point while adding the method to the observation dispatcher. Getting the class upon method call doesn't help me correctly call the method in the first place.
DGGenuine
Whoa, sorry for my laziness in not reading your entire question.
Will McCutchen
A: 

The problem is that when the decorator is called the class doesn't exist yet. Try this:

def loud_decorator(func):
    print("Now decorating %s" % func)
    def decorated(*args, **kwargs):
        print("Now calling %s with %s,%s" % (func, args, kwargs))
        return func(*args, **kwargs)
    return decorated

class Foo(object):
    class __metaclass__(type):
        def __new__(cls, name, bases, dict_):
            print("Creating class %s%s with attributes %s" % (name, bases, dict_))
            return type.__new__(cls, name, bases, dict_)

    @loud_decorator
    def hello(self, msg):
        print("Hello %s" % msg)

Foo().hello()

This program will output:

Now decorating <function hello at 0xb74d35dc>
Creating class Foo(<type 'object'>,) with attributes {'__module__': '__main__', '__metaclass__': <class '__main__.__metaclass__'>, 'hello': <function decorated at 0xb74d356c>}
Now calling <function hello at 0xb74d35dc> with (<__main__.Foo object at 0xb74ea1ac>, 'World'),{}
Hello World

As you see, you are going to have to figure out a different way to do what you want.

Ants Aasma
when one defines a function the function doesn't exist yet, but one is able to recursively call the function from within itself. I guess this is a language feature specific to functions and not available to classes.
DGGenuine
DGGenuine: The function is only called, and the function thus accesses itself, only after it was created completely. In this case, the class can not be complete when the decorator is called, since the class must wait for the decorator's result, which will be stored as one of the attributes of the class.
kaizer.se
A: 

As Ants indicated, you can't get a reference to the class from within the class. However, if you're interested in distinguishing between different classes ( not manipulating the actual class type object), you can pass a string for each class. You can also pass whatever other parameters you like to the decorator using class-style decorators.

class Decorator(object):
    def __init__(self,decoratee_enclosing_class):
        self.decoratee_enclosing_class = decoratee_enclosing_class
    def __call__(self,original_func):
        def new_function(*args,**kwargs):
            print 'decorating function in ',self.decoratee_enclosing_class
            original_func(*args,**kwargs)
        return new_function


class Bar(object):
    @Decorator('Bar')
    def foo(self):
        print 'in foo'

class Baz(object):
    @Decorator('Baz')
    def foo(self):
        print 'in foo'

print 'before instantiating Bar()'
b = Bar()
print 'calling b.foo()'
b.foo()

Prints:

before instantiating Bar()
calling b.foo()
decorating function in  Bar
in foo

Also, see Bruce Eckel's page on decorators.

Ross Rogers
Thanks for confirming my depressing conclusion that this isn't possible. I could also use a string that fully qualified the module/class ('module.Class'), store the string(s) until the classes have all fully loaded, then retrieve the classes myself with import. That seems like a woefully un-DRY way to accomplish my task.
DGGenuine
+4  A: 

If you are using Python 2.6 or later you could use a class decorator, perhaps something like this (warning: untested code).

def class_decorator(cls):
   for name, method in cls.__dict__.iteritems():
        if hasattr(method, "use_class"):
            # do something with the method and class
            print name, cls
   return cls

def method_decorator(view):
    # mark the method as something that requires view's class
    view.use_class = True
    return view

@class_decorator
class ModelA(object):
    @method_decorator
    def a_method(self):
        # do some stuff
        pass

The method decorator marks the method as one that is of interest by adding a "use_class" attribute - functions and methods are also objects, so you can attach additional metadata to them.

After the class has been created the class decorator then goes through all the methods and does whatever is needed on the methods that have been marked.

If you want all the methods to be affected then you could leave out the method decorator and just use the class decorator.

Dave Kirby
Thanks I think this is the route with which to go. Just one extra line of code for any class I'd want to use this decorator. Maybe I could use a custom metaclass and perform this same check during __new__...?
DGGenuine
Anyone trying to use this with staticmethod or classmethod will want to read this PEP: http://www.python.org/dev/peps/pep-0232/ Not sure it's possible because you can't set an attribute on a class/static method and I think they gobble up any custom function attributes when they are applied to a function.
DGGenuine