Search code examples
pythondecoratorpython-decoratorscode-inspection

Can I see what a Python decorator does to my code?


Is there a way to see what the application of a Python decorator has done with a function to which I have applied it. For example if I have

class A(object):

   @property
   def something(self):
       return 0

I'd like to see what the code that's executed for something actually looks like. Is there a way to do this?


Solution

  • A decorator doesn't produce code; a decorator is really only syntactic sugar:

    @property
    def something(self):
        return 42
    

    is really interpreted as:

    def something(self):
        return 42
    something = property(something)
    

    e.g. the expression following the @ sign is evaluated, and the result is called, passing in the function or class following the @ line. Whatever the decorator then returns replaces the original object.

    For introspection purposes, the @ line is not retained; you'd have to parse the source code itself to discover any decorators present. A decorator is not obliged to return a new object; you can return the original object unaltered and you cannot, with introspection, know the difference.

    Your best bet is to return to the source of the decorator then and just read the code. The property decorator is implemented in C, but the descriptor howto contains a Python implementation that does the same thing:

    class Property(object):
        "Emulate PyProperty_Type() in Objects/descrobject.c"
    
        def __init__(self, fget=None, fset=None, fdel=None, doc=None):
            self.fget = fget
            self.fset = fset
            self.fdel = fdel
            if doc is None and fget is not None:
                doc = fget.__doc__
            self.__doc__ = doc
    
        def __get__(self, obj, objtype=None):
            if obj is None:
                return self
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(obj)
    
        def __set__(self, obj, value):
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(obj, value)
    
        def __delete__(self, obj):
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(obj)
    
        def getter(self, fget):
            return type(self)(fget, self.fset, self.fdel, self.__doc__)
    
        def setter(self, fset):
            return type(self)(self.fget, fset, self.fdel, self.__doc__)
    
        def deleter(self, fdel):
            return type(self)(self.fget, self.fset, fdel, self.__doc__)