4

When you decorate a method, it is not bound yet to the class, and therefor doesn't have the im_class attribute yet. I looking for a way to get the information about the class inside the decorator. I tried this:

import types

def decorator(method):

    def set_signal(self, name, value):
        print name
        if name == 'im_class':
            print "I got the class"

    method.__setattr__ = types.MethodType(set_signal, method)

    return method


class Test(object):
    @decorator
    def bar(self, foo):
        print foo

But it doesn't print anything.

I can imagine doing this:

class Test(object):
    @decorator(klass=Test)
    def bar(self, foo):
        print foo

But if I can avoid it, it would make my day.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Bite code
  • 578,959
  • 113
  • 301
  • 329
  • How about doing something with the inspect module? you can do a decorator that retrieve info with this, anyway what information do yo request? – Netwave Oct 08 '12 at 09:26
  • Hmmm, why can not you use `type(self)` in the wrapper? – defuz Oct 08 '12 at 09:33
  • @defyz: Because I want to have access to the class outside of the method call. The purpose is to register the class to an observer, with the method being the callback. – Bite code Oct 08 '12 at 09:40
  • @Daniel: inspect doesn't have the information before the method is bound either. – Bite code Oct 08 '12 at 09:40

4 Answers4

3

__setattr__ is only called on explicit object.attribute = assignments; building a class does not use attribute assignment but builds a dictionary (Test.__dict__) instead.

To access the class you have a few different options though:

  1. Use a class decorator instead; it'll be passed the completed class after building it, you could decorate individual methods on that class by replacing them (decorated) in the class. You could use a combination of a function decorator and a class decorator to mark which methods are to be decorated:

    def methoddecoratormarker(func):
        func._decorate_me = True
        return func
    
    def realmethoddecorator(func):
        # do something with func. 
        # Note: it is still an unbound function here, not a method!
        return func
    
    def classdecorator(klass):
        for name, item in klass.__dict__.iteritems():
            if getattr(item, '_decorate_me', False):
                klass.__dict__[name] = realmethoddecorator(item)
    

    You could use a metaclass instead of a class decorator to achieve the same, of course.

  2. Cheat, and use sys._getframe() to retrieve the class from the calling frame:

    import sys
    
    def methoddecorator(func):
         callingframe = sys._getframe(1)
         classname = callingframe.f_code.co_name
    

    Note that all you can retrieve is the name of the class; the class itself is still being built at this time. You can add items to callingframe.f_locals (a mapping) and they'll be made part of the new class object.

  3. Access self whenever the method is called. self is a reference to the instance after all, and self.__class__ is going to be, at the very least, a sub-class of the original class the function was defined in.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ok, I'll accept this one, not for the _getframe but because I will fallback on class decorators. – Bite code Oct 08 '12 at 10:38
  • @e-satis: Registering the class is, afterall, effectively a class-level operation, so using a class decorator makes sense just like a metaclass would. – martineau Oct 08 '12 at 16:22
  • @e-satis: You could go one level of ugliness (creativeness) further, and stick a `__metaclass__` in the `callingframe.f_locals` dictionary, to assign a metaclass to the class.. – Martijn Pieters Oct 08 '12 at 16:26
  • I got a problem with metaclasses: I'm aiming django models, and they are already implemented using a metaclass. So I can't override it. It's funny, considering my answers on metaclass if probably the most popular Python answer ever (http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python). – Bite code Oct 08 '12 at 22:50
1

My strict answer would be: It's not possible, because the class does not yet exist when the decorator is executed.

The longer answer would depend on your very exact requirements. As I wrote, you cannot access the class if it does not yet exists. One solution would be, to mark the decorated method to be "transformed" later. Then use a metaclass or class decorator to apply your modifications after the class has been created.

Another option involves some magic. Look for the implementation of the implements method in zope.interfaces. It has some access to the information about the class which is just been parsed. Don't know if it will be enough for your use case.

Achim
  • 15,415
  • 15
  • 80
  • 144
0

You might want to take a look at descriptors. They let you implement a __get__ that is used when an attribute is accessed, and can return different things depending on the object and its type.

madjar
  • 12,691
  • 2
  • 44
  • 52
  • Yes, just like setattribute does, but in that particular case, it doesn't work. i'm gessing these attributes are set at the C implementation level, and don't trigger the descriptors. – Bite code Oct 08 '12 at 09:37
  • @e-satis: `__get__` works fine *when the method is retrieved via attribute access. Which is never, at class building time. – Martijn Pieters Oct 08 '12 at 09:59
0

Use method decorators to add some marker attributes to the interesting methods, and use a metaclass which iterates over the methods, finds the marker attributes, and does the logic. The metaclass code is run when the class is created, so it has a reference to the newly created class.

class MyMeta(object):
  def __new__(...):
    ...
    cls = ...
    ... iterate over dir(cls), find methods having .is_decorated, act on them
    return cls
def decorator(f):
  f.is_decorated = True
  return f
class MyBase(object):
  __metaclass__ = MyMeta
class MyClass(MyBase):
  @decorator
  def bar(self, foo):
    print foo

If you worry about that the programmer of MyClass forgets to use MyBase, you can forcibly set the metaclass in decorator, by exampining the globals dicitionary of the caller stack frame (sys._getframe()).

pts
  • 80,836
  • 20
  • 110
  • 183
  • this will work, but implies using a metaclass. The whole purpose of the exercice being making the observer registration easier, adding a meta class will not help. I already can register the class, I just want to make it more pleasant. – Bite code Oct 08 '12 at 10:02
  • Installing a metaclass automatically (by the decorator) is very easy to use, but complicated to implement. Other solutions are cumbersome to use, but easy to implement. You didn't mention which kind of easiness you are interested in, so I fail to see why my answer didn't answer your question. – pts Oct 08 '12 at 13:30
  • There is no way to add the metaclass to the class from the method decorator since you don't have access to the class. And if you use a class decorator, then you don't have a problem. – Bite code Oct 08 '12 at 22:48
  • ``There is no way to add the metaclass to the class from the method decorator'' -- this is false. Something like `sys._getframe().f_back.f_locals['__metaclass__'] = MyMeta` worked for me last time I needed that. – pts Oct 09 '12 at 11:05
  • Yeah but in that case we are back to Martijn Pieters's play ground :-) – Bite code Oct 09 '12 at 14:34
  • From your question it's not obvious if you need a reference to the class or the class name is enough. If you need a reference, then my answer solves your problem (and it is very easy to use, but ugly to implement), while Martijn Pieter's answers don't. This is the exact reason why I wrote my answer: it solves the problem if the requirements are refined in a very specific way. – pts Oct 10 '12 at 14:41
  • As I said in other comment, using a metaclass is out of the question as I'm aiming Django classes which already implement metaclasses. – Bite code Oct 10 '12 at 17:42