Hi!

# First simple idea:

TL;DR: Let's show a inspect.Signature instance to the help messages instead of just writing its actual "vague" signature [usually (*args, **kwargs)].

Suppose we have a decorator that returns a wrapper:

@functools.wraps(func)
def wrapper(*args, **kwargs):
    ... # some code

And suppose this decorator doesn't change the wrapped function signature (e.g. it just build some context or check some inputs). The code might have something like this (before returning the wrapper):

wrapper.__signature__ = inspect.signature(func)

It would help a lot if the list of valid arguments would be kept in the help message of the wrapped function.

If the wrapper function changes the signature, one could just create another inspect.Signature object to properly document its behavior. Setting the __signature__ would suffice for that. Actually, the same applies to other functions, not just wrappers. This might not be possible beforehand as the parameter names/kinds/defaults might be dynamic (e.g. the distinct signatures of the wrapped functions).

# Second idea:

The partial objects might include an automatic propagation of the __signature__, if there's one already set. That can be lazy (the partial object __signature__ is created only when requested).

# Third idea:

Setting the function signature itself from an inspect.Signature object.

That might be a new functools.wraps keyword argument, like:

@functools.wraps(wrapped_func, signature=wrapped_func.__signature__)
def wrapper_func(*args, **kwargs):
    ... # some code

Or perhaps a new syntax, like:

def wrapper_func(*args, **kwargs) with wrapped_func.__signature__:
    ... # some code

Where the signature object is an inspect.Signature instance, or perhaps a callable to have its signature copied from.

Externally, this would behave like a function with the given (more restrict) signature (e.g. raising TypeError if it doesn't match the given wrapped_func.__signature__ in the example). Internally, the function would just use the explicit args and kwargs (or whatever the explicit signature happen to be).

As an example, suppose the wrapped_func has the (a, b, c=3, *, d=4) signature, the wrapper_func of the proposed syntax would behave like this for this example:

def wrapper_func(a, b, c=3, *, d=4):
    def internal_wrapper_func(*args, **kwargs):
        ... # some code
    return internal_wrapper_func(a, b, c=3, *, d=4)

The most obvious use cases are the creation of simple function signature validators (to avoid losing a parameter left untouched in the kwargs dict, to "fail fast" and fix the argument name in code) and the propagation of help messages documentation (e.g. to avoid showing just a "**kwargs" when the valid keyword arguments can be grabbed from the function that uses them).

Regards,
Danilo J. S. Bellini
---------------
"It is not our business to set up prohibitions, but to arrive at conventions." (R. Carnap)