Preserving signatures of decorated functions Preserving signatures of decorated functions python python

Preserving signatures of decorated functions


  1. Install decorator module:

    $ pip install decorator
  2. Adapt definition of args_as_ints():

    import decorator@decorator.decoratordef args_as_ints(f, *args, **kwargs):    args = [int(x) for x in args]    kwargs = dict((k, int(v)) for k, v in kwargs.items())    return f(*args, **kwargs)@args_as_intsdef funny_function(x, y, z=3):    """Computes x*y + 2*z"""    return x*y + 2*zprint funny_function("3", 4.0, z="5")# 22help(funny_function)# Help on function funny_function in module __main__:# # funny_function(x, y, z=3)#     Computes x*y + 2*z

Python 3.4+

functools.wraps() from stdlib preserves signatures since Python 3.4:

import functoolsdef args_as_ints(func):    @functools.wraps(func)    def wrapper(*args, **kwargs):        args = [int(x) for x in args]        kwargs = dict((k, int(v)) for k, v in kwargs.items())        return func(*args, **kwargs)    return wrapper@args_as_intsdef funny_function(x, y, z=3):    """Computes x*y + 2*z"""    return x*y + 2*zprint(funny_function("3", 4.0, z="5"))# 22help(funny_function)# Help on function funny_function in module __main__:## funny_function(x, y, z=3)#     Computes x*y + 2*z

functools.wraps() is available at least since Python 2.5 but it does not preserve the signature there:

help(funny_function)# Help on function funny_function in module __main__:## funny_function(*args, **kwargs)#    Computes x*y + 2*z

Notice: *args, **kwargs instead of x, y, z=3.


This is solved with Python's standard library functools and specifically functools.wraps function, which is designed to "update a wrapper function to look like the wrapped function". It's behaviour depends on Python version, however, as shown below. Applied to the example from the question, the code would look like:

from functools import wrapsdef args_as_ints(f):    @wraps(f)     def g(*args, **kwargs):        args = [int(x) for x in args]        kwargs = dict((k, int(v)) for k, v in kwargs.items())        return f(*args, **kwargs)    return g@args_as_intsdef funny_function(x, y, z=3):    """Computes x*y + 2*z"""    return x*y + 2*z

When executed in Python 3, this would produce the following:

>>> funny_function("3", 4.0, z="5")22>>> help(funny_function)Help on function funny_function in module __main__:funny_function(x, y, z=3)    Computes x*y + 2*z

Its only drawback is that in Python 2 however, it doesn't update function's argument list. When executed in Python 2, it will produce:

>>> help(funny_function)Help on function funny_function in module __main__:funny_function(*args, **kwargs)    Computes x*y + 2*z


There is a decorator module with decorator decorator you can use:

@decoratordef args_as_ints(f, *args, **kwargs):    args = [int(x) for x in args]    kwargs = dict((k, int(v)) for k, v in kwargs.items())    return f(*args, **kwargs)

Then the signature and help of the method is preserved:

>>> help(funny_function)Help on function funny_function in module __main__:funny_function(x, y, z=3)    Computes x*y + 2*z

EDIT: J. F. Sebastian pointed out that I didn't modify args_as_ints function -- it is fixed now.