[Python-Dev] dealing with decorators hiding metadata of decorated functions

Nick Coghlan ncoghlan at gmail.com
Sat Mar 18 06:40:09 CET 2006


Josiah Carlson wrote:
> "Brett Cannon" <brett at python.org> wrote:
>> With the discussion of a possible @decorator to help set the metadata
>> of the decorator to that of what the wrapped function has, I had an
>> idea that I wanted to toss out there (this dicussion stems from a blog
>> post I made: http://sayspy.blogspot.com/2006/03/how-to-handle-object-identity-issues.html).
> 
> [snip]
> 
> Edward Loper suggested this way back on September 5, 2004.
> 
>     http://mail.python.org/pipermail/python-dev/2004-September/048626.html
> 
> I was and continue to be +1 on this,

+1 here, too. Unlike Brett, though, I have no problem with overwriting 
__name__ and updating __dict__ unconditionally, and overwriting __doc__ if it 
hasn't already been set.

The first two are needed if we expect "print f" and "f.a" to work properly. 
The function's name is set on the 'def' line, and I'd be much happier seeing 
that at all levels of the decorator chain, rather than seeing something like 
"wrapper", "wrapper", ... "wrapper", "f". Annotating decorators will modify 
the functions' attributes, and this needs to be visible in the final 
function's dictionary.

If a wrapper doesn't set a docstring explicitly, it makes a lot more sense to 
me to re-uses the original function's docstring rather than leave it at None.

My real interest is that it should be possible to get at all the details of 
the original function (such as its code object), and the obvious way to do 
that is with a standard attribute that links to the original.

> though I would go farther and state,
> like I did at the time, that one shouldn't copy any of the function
> attributes, they should come 'free', similar to the way that class
> attributes are 'free' on subclasses.

Well, that's the idea behind the decorator decorator - simply put @decorator 
on your decorator function and it will automatically do the right thing.

>     http://mail.python.org/pipermail/python-dev/2004-September/048631.html
> 
> What would make this _really_ nice is if one didn't need to do anything
> manually; that the attribute that pointed to the decorated
> function/object would be automatically applied - though I realize that
> this may not be generally possible.

A slight problem is that not all decorators will wrap the function they 
decorate - some will only annotate it.

However, here's an idea for the @decorator decorator that would make it pretty 
much automatic, leaves the docstring alone if the decorator has already set it 
on the wrapper, and builds up a record of the decorators that are wrapping the 
the original function:

def _link_decorated(decorated, orig, decorator):
       """Link a decorated function with the original"""
       decorated.__name__ = orig.__name__
       decorated.__dict__.update(orig.__dict__)
       if decorated.__doc__ is None:
           decorated.__doc__ = orig.__doc__
       decorated.__decorates__ = orig
       decorated.__decorator__ = decorator

def decorator(orig_decorator):
       """Decorator to create a well-behaved decorator"""
       # Wrapper function that links a decorated function
       # to the original function if necessary
       def wrapper(f):
           decorated = orig_decorator(f)
           if decorated is not f:
               # Link wrapper function to the original
               _link_decorated(decorated, f, wrapper)
           return decorated
       _link_decorated(wrapper, orig_decorator, decorator)
       return wrapper

Decorators that only do annotations aren't recorded because there isn't 
anywhere to record them. Wrapping decorators, on the other hand, allow the 
references to both the decorated function and the applied decorator to be 
stored on the new function object.

Cheers,
Nick.

P.S. Example usage:

Py> @decorator
... def annotated(f):
...     f.note = 1
...     return f
...
Py> @decorator
... def wrapped(f):
...     def wrapper(*args, **kwds):
...         return f(*args, **kwds)
...     return wrapper
...

Py> @wrapped
... @annotated
... @wrapped
... @wrapped
... def show(*args, **kwds):
...     print args, kwds
...

Py> while hasattr(obj, "__decorates__"):
...     print obj
...     print "  Decorates:\t%s" % obj.__decorates__
...     print "  Using:\t%s" % obj.__decorator__
...     print "  Annotated?:\t%s" % hasattr(obj, "note")
...     print
...     obj = obj.__decorates__
...
<function show at 0x00AE9D30>
   Decorates:    <function show at 0x00AE9AB0>
   Using:        <function wrapped at 0x00AE9CF0>
   Annotated?:   True

<function show at 0x00AE9AB0>
   Decorates:    <function show at 0x00AE9B30>
   Using:        <function wrapped at 0x00AE9CF0>
   Annotated?:   True

<function show at 0x00AE9B30>
   Decorates:    <function show at 0x00AE99F0>
   Using:        <function wrapped at 0x00AE9CF0>
   Annotated?:   False


-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-Dev mailing list