Skip to content

Add a stdlib decorator that copies/applies the ParameterSpec from one function to another #107001

@CarliJoy

Description

@CarliJoy

Feature or enhancement

Add an decorator that copies the parameter specs from one function onto another:

Example implementation

from collections.abc import Callable
from typing import ParamSpec, TypeVar, cast, Any


P = ParamSpec("P")
T = TypeVar("T")


def copy_kwargs(
    kwargs_call: Callable[P, Any]
) -> Callable[[Callable[..., T]], Callable[P, T]]:
    """Decorator does nothing but returning the casted original function"""

    @wraps(kwargs_call)
    def return_func(func: Callable[..., T]) -> Callable[P, T]:
        return cast(Callable[P, T], func)

    return return_func

Alternative names of copy_kwargs could be apply_parameters

Pitch

A quite common pattern in Python is to create a new function enhanced that enhanced a given function original or method and passes arguments using *args, **kwargs.
This way the signature of original can be adopted without changing the signature of enhanced.
That is especially useful if you enhance a 3rd party function.

A downside of this pattern is, that static type checkers (and IDE) are unable to detect if the correct parameters were used or give type/autocomplete hints.

Adding this pattern allows static type checkers and IDE to give correct parameter hints and check them.

Previous discussion

Discussed with @ambv at a Sprint within Europython 2023

Specification

This function is very simple, so adding it every project that requires it would be very simple.
A reason to add it to the standard library is that type checkers can/should check, that the applied parameter spec matches the one of the function.

In example:

# Our test function for kwargs
def source_func(foo: str, bar: int, default: bool = True) -> str:
    if not default:
        return "Not Default!"
    return f"{foo}_{bar}"

@copy_kwargs(source_func)
def kwargs_test(**kwargs) -> float:
    print(source_func(**kwargs))
    return 1.2

kwargs_test("a", 2) # raises a TypeError but does not produce any TypingError atm

But if source_func would be defined as

def source_func(*, foo: str, bar: int, default: bool = True) -> str:
    ...

The type checker would complain.

So I would suggest that type checkers check if the wrapped functions signature is compatible to the sourced function signature.
Which is separate discussion from including it into the stdlib.

The documentation should include a hint, that *args and **kwargs should be always added to the applied function.

Related Discourse Threads: #

Related Issues:

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibPython modules in the Lib dirtopic-typingtype-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions