-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a stdlib decorator that copies/applies the ParameterSpec from one function to another #107001
Comments
The function should actually go to |
We occasionally backport typing-adjacent things from other modules in typing_extensions ( |
I added it to typing as in opposite to |
I am wondering if it makes sense to add a See mypy play, pyright play @overload
def copy_kwargs(
kwargs_call: Callable[P, Any], keep_first: Literal[False] = False
) -> Callable[[Callable[..., T]], Callable[P, T]]:
...
@overload
def copy_kwargs(
kwargs_call: Callable[Concatenate[TFirstSource, P], Any],
keep_first: Literal[True],
) -> Callable[
[Callable[Concatenate[TFirstTarget, P2], T]],
Callable[Concatenate[TFirstTarget, P], T],
]:
...
def copy_kwargs(
kwargs_call: Callable, keep_first: bool = False
) -> Callable[[Callable], Callable]:
"""Decorator does nothing but returning the casted original function"""
def return_func(func: Callable[..., T]) -> Callable[P, T]:
return cast(Callable[P, T], func)
return return_func This can be used for something like this: class Foo:
def upstream_method(self, a: int, b: float, *, double: bool = False) -> float:
return 1.0
class Bar:
def __init__(self):
self.foo = Foo()
@copy_kwargs(Foo.upstream_method, keep_first=True)
def enhanced(
self, a: int, b: float, *args: Any, double: bool = False, **kwargs: Any
) -> str:
return str(self.foo.upstream_method(a, b, *args, double=double, **kwargs)) Otherwise at least MyPy and PyRight complains about |
How would a case like this be covered? Where you're extending the wrapped function with your own params? For example, consider this wrapper around httpx.Client: from collections.abc import Callable
from functools import wraps
from typing import Any, ParamSpec, TypeVar, cast
from httpx import Client
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
class APIWrapper:
@copy_kwargs(Client)
def __init__(self, api_url: str = "https://example.com/api/", ratelimit: int = 2, **kwargs: Any) -> None:
self.api_url = api_url
self.ratelimit = ratelimit
self.kwargs = kwargs
def post(self) -> str:
with Client(**self.kwargs) as session:
session.post(self.api_url)
return "POST"
APIWrapper(api_url="https://example.org/api") # Unexpected keyword argument "api_url" for "APIWrapper" |
The decorator is called But using the decorator for See a similar example working on MyPy Play I guess I will add the version with Back to your question. You can use def copy_kwargs_and_add_str_argument(
kwargs_call: Callable[Concatenate[TFirstSource, P], Any],
) -> Callable[
[Callable[Concatenate[TFirstTarget, P2], T]],
Callable[Concatenate[TFirstTarget, str, P], T],
]:
"""Decorator does nothing but returning the casted original function"""
def return_func(func: Callable[..., T]) -> Callable[P, T]:
return func
return return_func # type: ignore Please open a new Issue or Discourse Thread (there might be already one, see links in description, use search first!) to suggest changes that can combine / extent signatures. Further discussion please only about non-modifying copies (besides handling the general case of |
Thank you for the detailed example! My apologies for going off-topic initially, this will be my last off-topic message just to add some of the discussion links I found relating to my usecase: |
Feature or enhancement
Add an decorator that copies the parameter specs from one function onto another:
Example implementation
Alternative names of
copy_kwargs
could beapply_parameters
Pitch
A quite common pattern in Python is to create a new function
enhanced
that enhanced a given functionoriginal
or method and passes arguments using*args, **kwargs
.This way the signature of
original
can be adopted without changing the signature ofenhanced
.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:
But if
source_func
would be defined asThe 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:
same_definition_as_in
bound
toParamSpec
Related Issues:
Linked PRs
The text was updated successfully, but these errors were encountered: