[Python-Dev] possible use of __decorates__ in functools.decorator

Luis P Caamano lcaamano at gmail.com
Sun May 7 17:35:26 CEST 2006


In a previous post related to the functools.decorator function, I
think Nick was wondering if the  __decorator__ and __decorates__
attributes were useful and Guido was tempted to call YAGNI on them.

Coincidentally, I've run into a situation where I had to use the
__decorates__ attribute, which I'd like to show to you because it
might be a good example of why __decorates__ is useful or perhaps
there's another way to do what I'm doing without it that some of you
could hint at.

We have a tracing decorator that automatically logs enter/exits
to/from functions and methods but it also figures out by itself the
arguments values of the function call and the class or module the
function/method is defined on.  I thought this was trivial until I ran
into methods extended in subclasses, at which point we had to deal
with bases and the mro.

The part that figures out the name of the class where the wrapped
method was defined has to start with the assumption that the first
argument is self and then find the defining class from it.  This code
fragment is in the wrapper function of the decorator and it looks like
this:

                if numargs > 0:
                    # at definition time, class methods are not methods
                    # yet because the class doesn't exist when the
                    # decorators get called and thus, we have to figure
                    # out classname at runtime via self, which is then
                    # an instance of a class.
                    #
                    # assume first arg is self, see if f.__name__ is there
                    # as a method and if so, then grab it's class name
                    #
                    self = args[0]

                    if type(self) == types.InstanceType:
                        # getattr will find the method anywhere in the
                        # class tree so start from the top
                        bases = list(inspect.getmro(self.__class__))
                        bases.reverse()
                        for c in bases:
                            # f was given to us in the deco_func
                            meth = getattr(c, f.__name__, None)

                            # we found a method with that name, which
                            # it's probably this same wrapper function
                            # we wrapped the original method with.

                            ofunc = getattr(meth, '__decorates__', False)

                            if ofunc and ofunc.func_code == f.func_code:
                                # got it
                                clsname = meth.im_class.__name__
                                break
                        del c, meth
                    del self

This tracing code will correctly show calls to foomethod with the
appropriate class name.

    def testMethodInBothClasses(self):
        class Base:
            @tracelevel(1)
            def foomethod(self, x, y=5, **kwds):
                return x+y

        class TClass(Base):
            @tracelevel(1)
            def foomethod(self, x, y=1, **kwds):
                return Base.foomethod(self, x, **kwds) + x + y

        t = TClass()
        rc = t.foomethod(4, d=1)
        self.failUnless(rc == 14)
        return

Is there a way to do this without the __decorates__ attribute?

--
Luis P Caamano
Atlanta, GA USA


More information about the Python-Dev mailing list