-
Notifications
You must be signed in to change notification settings - Fork 237
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
ParamSpec: use P.args and P.kwargs in other scopes, such as return types #1252
Comments
I remember seing patterns like that in |
This option has already been discussed and rejected in PEP 612. To quote from the discussion:
As an example, for the following code:
Should we reveal a tuple of int + empty dict? Or should we reveal an empty tuple and a singleton dict that maps To fully resolve this kind of issue, there needs to be a way in the type system to represent positional-only, positional-or-keyword, and keyword-only parameters separately. But at that point, it's going to be a different language feature, not |
This came up in the context of ORMs - e.g. something vaguely like T = TypeVar('T', bound=type)
P = ParamSpec('P')
# Attribute[T] = table field of type T
def find(table: T, selection: Any, *projection: Map[Attribute, P.args]) -> Map[tuple, P.args]:
# SELECT projection FROM table WHERE selection
return # values from the selection, with same types as the attributes
# e.g.
from attrs import define
@define
class Strings:
__collection__ = 'strings'
id: int
string: str
# Strings.id is an Attribute[int], etc
result: tuple[int, str] = find(Strings, ..., Strings.id, Strings.string) # should typecheck |
@Kenny2github That does not work because, it's invalid to use
So again the issue is that there's no unambiguous way to split a parameter list cleanly into a The use case you mentioned is better handled by the (yet-to-be-pepified) "map" operator extension for list variadics. |
So then is it solvable if we instead return an object that represents the entire set of arguments, such as a tuple that holds both the args and the kwargs? That object could then be considered For example: def call_it(func: Callable[P, T], args: P) -> T:
print("calling", func)
return func(*args[0], **args[1])
def get_callable_and_args() -> Tuple[Callable[P, Any], P]:
return complex, (('foo',), {'reverse': True}) |
@chadrik That won't work either: |
What if we create a new type of object which represents "parameters that are compatible with ParamSpec For example: P = ParamSpec('P')
ParamsP = Parameters(P)
def call_it(func: Callable[P, T], args: ParamsP) -> T:
print("calling", func)
return func(*args.args, **args.kwargs)
def get_callable_and_args() -> Tuple[Callable[P, Any], ParamsP]:
return complex, ParamsP(('foo',), {'reverse': True})
func, args = get_callable_and_args()
reveal_type(args.args) # prints: Tuple[str, ...]
reveal_type(args.kwargs) # prints: Dict[str, Any]
call_it(func, args)
In the above example, it doesn't matter that we don't know all of the ways to split apart
class ParamsP(NamedTuple):
args: Tuple[str, ...]
kwargs: Dict[str, Any] There is no inference or guarantees about the structure of |
That only works if your function is a "consumer" of |
I've been thinking lately that perhaps the best way to pass around a function and its arguments prior to invoking it is a The |
Yes, we don't have mypy>0.950 support yet, but it is planned. |
Here's the ticket for the |
I would find something like this to be very useful. I'm am creating a react-like framework in Python and one thing I want is to be able to create a decorator that marks functions as HTML-like element constructors. The naive approach is to use div(
child,
div(
child,
child,
child,
child,
),
child,
child,
child,
child,
child,
child,
...,
# props for the outer-most element live all the way down here
id="something",
) Instead, it would be better if the following could be achieved: @component
def div(child1: str, child2: str, *, attr1: int, attr2 int) -> str:
...
div({"attr1": 1, "attr2": 2}, "hello", "world") If this feature were available, it seems like I could write P = ParamSpec("P")
R = TypeVar("R")
def component(func: Callable[P, R]) -> ElementConstructor[P, R]
...
R_co = TypeVar("R_co", covarient=True)
class ElementConstructor(Protocol[P, R]):
def __init__(attributes: P.kwargs, *children: P.args) -> R:
... |
This pattern often comes up in task queue libraries. Huey, Celery, Dramatiq and Rq all have a way of turning a function into a task where you call the task using Edit: |
I'm realizing that positional or keyword parameters would likely make implementing this rather complicated. The fact that a parameter can be either positional or a keyword, but not both, means that P = ParamSpec("P")
R = TypeVar("R")
def split_args_kwargs(*args: P.args, **kwargs: P.kwargs) -> Callable[[P.args], Callable[[P.kwargs], R]]:
...
def f(a: int, b: int, c: int) -> int:
...
g = split_args_kwargs(f)
h = g(1)
h({"a": 1, "b": 2, "c": 3}) # error It's possible that this is not a significant technical challenge, but I don't know enough about how MyPy works to say. |
I have a number of cases where it would be very useful to use
P.args
andP.kwargs
of aParamSpec
to annotate tuple and dict objects, for example, when extracting arguments to pass to a function in another context.Here's an example:
In this scenario
P.args
andP.kwargs
represent a kind of ad-hocNamedTuple
andTypedDict
, respectively.Does this seem like a reasonable extension for
ParamSpecs
?The text was updated successfully, but these errors were encountered: