3

When I wrap a function with @, how do I make the wrapper function look & feel exactly like the wrapped function? help(function) in particular.

Some code:

>>> def wraps(f):
    def call(*args, **kw):
        print('in', f, args, kw) # example code. I need to transfer the arguments to another process and pickle them.
        return f(*args, **kw)
    return call

>>> def g():pass

>>> @wraps
def f(a, b = 1, g = g, *args, **kw):
    pass

>>> help(f)
Help on function call in module __main__:

call(*args, **kw) # this line bothers me. It should look different, look below

>>> def f(a, b = 1, g = g, *args, **kw):
    pass

>>> help(f)
Help on function f in module __main__:

f(a, b=1, g=<function g at 0x02EE14B0>, *args, **kw) # help(f) should look like this.

Motivation: It would also be nice to see the arguments when the help window pops up, when I type f( * plopp * I see (a, b = 1, g = g, *args, **kw). (in this case in the IDLE Python Shell)

I had a look at the inspect module which helps me with nice formatting. The problem is still there: how do I do this with arguments..

Default mutable argument passing like def f(d = {}): does not need to work since I transfer the arguments to another process and the identity would be lost anyway.

User
  • 14,131
  • 2
  • 40
  • 59
  • You'll need to define very carefully what you mean by "exactly". Given the level of introspection possible in Python, it's very difficult to make the *wrapped* (not modified, wrapped) function behave identically to the original. – chepner May 31 '14 at 20:02
  • For an in-depth analysis of how to make a well behaved decorator that actually preserves properly introspection abilities of a wrapped function see https://github.com/GrahamDumpleton/wrapt/tree/master/blog – Graham Dumpleton Sep 22 '14 at 10:01

3 Answers3

3

functools.wraps can be used to copy the name and docstring of the function. Copying the original function signature is considerably harder to do from scratch.

If you use the third-party decorator module, however, then

import decorator


@decorator.decorator
def wraps(f):
    def call(*args, **kw):
        print('in', f, args, kw) 
        return f(*args, **kw)
    return call


def g():pass

@wraps
def f(a, b = 1, g = g, *args, **kw):
    pass

help(f)

yields

Help on function f in module __main__:

f(a, b=1, g=<function g>, *args, **kw)
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
2

Use functools.wraps:

from functools import wraps

def wrapper(f):
    @wraps(f)
    def call(*args, **kw):
        print('in', f, args, kw)
        return f(*args, **kw)
    return call

@wrapper
def f(a, b = 1, g = g, *args, **kw):
    pass

help(f)
Help on function f in module __main__:

f(a, b=1, g=<function g at 0x7f5ad14a6048>, *args, **kw)

This preserves the __name__ and __doc__ attributes of your wrapped function.

roippi
  • 25,533
  • 4
  • 48
  • 73
1

I think the other answers are preferable, but if for some reason you don't want to use an external module, you could always alter your decorator like so:

def wraps(f):
  def call(*args, **kw):
    print('in', f, args, kw)
    return f(*args, **kw)
call.__name__ = f.__name__
call.__doc__ = f.__doc__
return call
whereswalden
  • 4,819
  • 3
  • 27
  • 41