Description
There is currently a difference in behaviour between collections.abc.Callable
and typing.Callable
for the following edge case involving ParamSpec
substitution:
Running PGUpdate|x64 interpreter...
Python 3.12.0a6+ (heads/main:12226bec25, Mar 10 2023, 17:32:23) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing as t, collections.abc as c
>>> P, T = t.ParamSpec("P"), t.TypeVar("T")
>>> t.Callable[P, T][[P, str], bool][int]
typing.Callable[[int, str], bool]
>>> c.Callable[P, T][[P, str], bool][int]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<frozen _collections_abc>", line 501, in __getitem__
File "<frozen _collections_abc>", line 456, in __new__
TypeError: Callable must be used as Callable[[arg, ...], result].
According to PEP-612, this is an invalid substitution, for two reasons:
- Parameters can only be prepended to a
ParamSpec
in a parameters list usingtyping(_extensions).Concatenate
. - Appending parameters to a
ParamSpec
in a parameters list is disallowed.
As such, the behaviour of collections.abc.Callable
is more correct here, so ideally we'd change the behaviour of typing.Callable
to match collections.abc.Callable
.
However, this error should hopefully be caught by static type checkers anyway, and this is a false negative rather than a false positive. The runtime makes no promises that it will raise TypeError
on all invalid substitutions, so fixing this should be low priority, in my opinion. We should first concentrate on fixing substitutions where the runtime raises exceptions, even though it shouldn't. For example:
This discrepancy in behaviour was first uncovered as part of a broader discussion in:
Cc. @sobolevn