-
-
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
Substitution of ParamSpec in Concatenate #88954
Comments
Substitution of ParamSpec in Concatenate produces weird results: >>> import typing
>>> P = typing.ParamSpec('P')
>>> typing.Concatenate[str, P][int]
typing.Concatenate[str, int]
>>> typing.Concatenate[str, P][[int]]
typing.Concatenate[str, (<class 'int'>,)]
>>> typing.Concatenate[str, P][typing.Concatenate[int, P]]
typing.Concatenate[str, typing.Concatenate[int, ~P]] But all these results are invalid: >>> typing.Concatenate[str, int]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/serhiy/py/cpython/Lib/typing.py", line 309, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 400, in __getitem__
return self._getitem(self, parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 595, in Concatenate
raise TypeError("The last parameter to Concatenate should be a "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: The last parameter to Concatenate should be a ParamSpec variable.
>>> typing.Concatenate[str, (int,)]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/serhiy/py/cpython/Lib/typing.py", line 309, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 400, in __getitem__
return self._getitem(self, parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 595, in Concatenate
raise TypeError("The last parameter to Concatenate should be a "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: The last parameter to Concatenate should be a ParamSpec variable.
>>> typing.Concatenate[str, [int]]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/serhiy/py/cpython/Lib/typing.py", line 309, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 400, in __getitem__
return self._getitem(self, parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 595, in Concatenate
raise TypeError("The last parameter to Concatenate should be a "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: The last parameter to Concatenate should be a ParamSpec variable.
>>> typing.Concatenate[str, typing.Concatenate[int, P]]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/serhiy/py/cpython/Lib/typing.py", line 309, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 400, in __getitem__
return self._getitem(self, parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 595, in Concatenate
raise TypeError("The last parameter to Concatenate should be a "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: The last parameter to Concatenate should be a ParamSpec variable. I expect that
Concatenate[str, Concatenate[int, P]] -> Concatenate[str, int, P]
Concatenate[str, [int, dict]] -> [str, int, dict]
Concatenate[str, ...] |
Should Concatenate support substitution to begin with? PEP-612 doesn't say anything, and I am fairly certain it's a special typing form, not a generic. So I don't really understand what it means to substitute Concatenate. Then again, Callable with a nested Concatenate can be substituted, and we currently implement that by using Concatenate's substitution behavior as proxy. But again, the meaning isn't clear to me. I also noticed this strange part in PEP-612 about user-defined generic classes: " ... def f(x: X[int, Concatenate[int, P_2]]) -> str: ... # Accepted (KJ: What?)
... The grammar for parameters_expression ::= I'm very confused. Does this mean Concatenate is valid when substituting user generics? Maybe I should ask the PEP authors? My general sense when I implemented the PEP was that it was intended primarily for static type checking only. IMO, runtime correctness wasn't its concern. |
My understanding is that type expression is valid when substitute TypeVar and parameters expression is valid when substitute ParamSpec. |
PR 27518 fixes a substitution of a ParamSpec variable with a Concatenate nad a list of types. It is not specified explicitly in PEP-612, but it does not contradict it. PR 30969 makes an ellipsis be valid as the last argument of Concatenate to fix a substitution of a ParamSpec variable with an ellipsis. It contradicts with PEP-612 which allows only a ParamSpec variable there. |
Before you start supporting things that contradict PEP-612 this should be discussed on typing-sig (or in the python/typing tracker). Honestly I'd feel more comfortable if there was agreement on typing-sig with your previous PRs in this issue as well; "it does not contradict it" is not a ringing endorsement. :-) |
I'm looking at #30969 and I'm not sure what the motivation for the change is. PEP-612 is quite precise here (https://www.python.org/dev/peps/pep-0612/#id1) and allows only a ParamSpec as the last argument to Concatenate. What is the use case for using ... as the last argument? What should it mean to a static type checker? |
Concatenate[int, ...] I would interpret as a function signature with first argument int, followed by arbitrary arguments afterwards. I haven't run into this case, but ... is allowed in other spots Paramspec is allowed currently. P = Paramspec("P")
class Foo(Generic[P]):
... Foo[...] # Allowed Are there any other places a paramspec is allowed? Generic type argument, first argument to callable, last to concatenate, anything else? I'm unaware of any type checking use case for Concatenate[int, ...]. You can practically get same thing by using a paramspec variable without using it elsewhere so that it captures, but then effectively discards that information. def use_func1(f: Callable[Concatenate[int, P], None]) -> None:
...
def use_func2(f: Callable[Concatenate[int, ...], None]) -> None:
... feels like those two signatures should encode same information. |
I still haven't seen a compelling reason to allow substituting |
I just talked to @pradeep90 and he is actually on board with allowing this; it seems like a reasonable generalization, and we can allow it at runtime already pending type checker support. |
Mind summarizing the use case here? Maybe give a simple example? |
Pradeep showed me an email thread from a few months ago (I think you were also on it) that showed a use case. I don't recall the exact code, but basically it would allow pyre to infer |
Sure. I wish we had allowed spelling that as |
@JelleZijlstra Reproducing the email thread (from August 2021)… for future reference: Mark Mendoza said:
My reply to Serhiy's question: Concatenate and
|
I do not think we need this. We need a way to express the result of The only problem was with ellipsis. There is no valid syntax to express the result. The specification should be extended to allow either |
I believe that we eventually arrived on consensus here that the changes made were acceptable. (Apologies if this summary is inaccurate.) Is there anything left to do here from a CPython/runtime perspective, or can this now be closed? Discussions about whether the PEP should be updated should probably continue over at |
Closing for now. Feel free to reopen (or open a new issue at python/typing) if there's still more to discuss :) |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: