Decorator with function annotations

decorator, python, python-3.x

Solution

New in version 3.3,`inspect.signature()`would allow you get the information you want in a function decorator. Here's an example of using it to print the argument names and values passed on each call to a decorated function as well as access the associated annotations:

import functools
import inspect

def verbose(wrapped):
    @functools.wraps(wrapped)  # optional - make wrapper look like wrapped
    def wrapper(*args):
        print('inside wrapper:')
        fsig = inspect.signature(wrapped)
        parameters = ', '.join('{}={}'.format(*pair)
                               for pair in zip(fsig.parameters, args))
        print('  wrapped call to {}({})'.format(wrapped.__name__, parameters))
        for parameter in fsig.parameters.values():
            print("  {} param's annotation: {!r}".format(parameter.name,
                                                         parameter.annotation))
        result = wrapped(*args)
        print('  returning {!r} with annotation: {!r}'.format(result,
                                                         fsig.return_annotation))
        return result
    return wrapper

@verbose
def xXy(x: 'x in X\'s', y: 'y in Y\'s') -> ('x times y','in X and Y units'):
    return x*y

xy = xXy(10, 3)
print('xXy(10, 3) -> {!r}'.format(xy))

Output:

inside wrapper:
  wrapped call to xXy(x=10, y=3)
  x param's annotation: "x in X's"
  y param's annotation: "y in Y's"
  returning 30 with annotation: ('x times y', 'in X and Y units')
xXy(10, 3) -> 30

Problem

In PEP 3107 and this SO answer it is implied that Python3K function annotations and decorators fit hand and glove -- that I should be able to write a decorator that works with function attributes. I cannot figure how to make them work as I expect however. Consider: ``` def verbose(lcls): def wrap(f): print('inside wrap') def wf(*args): print('inside wf') print('lcls in wf:',lcls) print('locals in wf:',locals()) print('wf annotations:',wf.__annotations__) print('xYx annotations:',xXy.__annotations__) r=f(*args) print('after f(*args)') return r return wf return wrap @verbose(locals()) def xXy(x: 'x in x\'s', y: 'y in Y\'s') -> ('x times y','in x and y units'): print('locals in xXy:',locals()) return x*y xy=xXy(10,3) print(xy) ``` Prints: ``` inside wrap inside wf lcls in wf: {'xXy': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>, '__doc__': None, 'verbose': <function verbose at 0x109767050>, '__cached__': None, '__builtins__': <module 'builtins'>, '__package__': None, '__file__': '/private/var/folders/gx/gqtmx9mn7b75pk1gfy0m9w3w0000gp/T/Cleanup At Startup/test-383453350.857.txt', '__loader__': <_frozen_importlib.SourceFileLoader object at 0x10959ac10>, '__name__': '__main__'} locals in wf: {'f': <function xXy at 0x109767e60>, 'args': (10, 3), 'lcls': {'xXy': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>, '__doc__': None, 'verbose': <function verbose at 0x109767050>, '__cached__': None, '__builtins__': <module 'builtins'>, '__package__': None, '__file__': '/private/var/folders/gx/gqtmx9mn7b75pk1gfy0m9w3w0000gp/T/Cleanup At Startup/test-383453350.857.txt', '__loader__': <_frozen_importlib.SourceFileLoader object at 0x10959ac10>, '__name__': '__main__'}, 'wf': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>} wf annotations: {} xYx annotations: {} locals in xXy: {'y': 3, 'x': 10} after f(*args) 30 ``` What that group of lines shows me is that I cannot see how to access the value of x and y in xXy in the decorator or the function attributes of xXy. What I would like to do is 1) have a function with annotations as specified in PEP 3107, 2) be able to have a decorator that can access the function annotations and the values the function is called with without just being a clone of of xXy's function signature.

Original source

Related problems