Skip to content
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

Question about the representation of ParamSpec Generic classes #1274

Closed
Gobot1234 opened this issue Oct 23, 2022 · 3 comments
Closed

Question about the representation of ParamSpec Generic classes #1274

Gobot1234 opened this issue Oct 23, 2022 · 3 comments
Labels
topic: other Other topics not covered

Comments

@Gobot1234
Copy link
Contributor

Gobot1234 commented Oct 23, 2022

While working on PEP 696 (TypeVarLike defaults) one point I've been particularly confused about how was the types that should be used for Generics with ParamSpecs in __parameters__ subscription.

Should you pass a list to the list as suggested in the PEP, types.GenericAlias and by mypy

class X(Generic[P]): ...

# from pep 612
def f(x: X[[int, bool]]) -> str: ...
reveal_type(X[[int, bool]]())  # mypy: revealed type is __main__.X[[builtins.int, builtins.bool]]

# using types.GenericAlias
class X_types:
    __class_getitem__ = classmethod(types.GenericAlias)
reveal_type(X_types[[int, bool]])  # runtime: X_types[[<class 'int'>, <class 'bool'>]]
reveal_type(X_types[(int, bool)])  # runtime: X_types[<class 'int'>, <class 'bool'>]

or use a tuple as the typing runtime suggests and by pyright

reveal_type(X[[int, bool]])  # runtime: revealed type is X[(<class 'int'>, <class 'bool'>)]
reveal_type(X[[int, bool]])  # pyright: revealed type is type[X[(int, bool)]]

This may seem relatively inconsequential and at runtime it probably is but after talking to @erictraut he brought up that PEP 696 supports both lists and tuples as the defaults for ParamSpec (https://peps.python.org/pep-0696/#paramspec-defaults) and this will be harder to support in pyright but I'd really like symmetry with the type of default and the type that you should pass to a subscription. Ideally I think I tuples would be the best type for for default just cause they should be the easiest to analyse and they are the closest in style to a PEP 677 (Callable Type Syntax) syntax even if the current syntax does look more like Callable's current syntax. Does anyone from the pyre team or from mypy have any particular opposition to changing to just supporting tuples in default?

@JelleZijlstra
Copy link
Member

I think a list representation is more consistent with the text of PEP 612 and the runtime representation of Callable. We should change the runtime repr of ParamSpec generics to be more like Callable, because right now they're inconsistent:

>>> Callable[[int, str], bool]
typing.Callable[[int, str], bool]
>>> P = ParamSpec("P")
>>> T = TypeVar("T")
>>> class MyCallable(Generic[P, T]): pass
... 
>>> MyCallable[[int, str], bool]
__main__.MyCallable[(<class 'int'>, <class 'str'>), bool]
>>> get_args(Callable[[int, str], bool])
([<class 'int'>, <class 'str'>], <class 'bool'>)
>>> get_args(MyCallable[[int, str], bool])
((<class 'int'>, <class 'str'>), <class 'bool'>)

@gvanrossum
Copy link
Member

IIRC there was a specific reason to use a list for ParamSpec. The runtime cannot tell the difference between X[Y, Z] and X[(Y, Z)] (this is independent of the type of X -- it's a general problem with __getitem__).

To avoid this ambiguity I recommend using a list, as Jelle wrote.

@Gobot1234
Copy link
Contributor Author

This has been changed as of python/peps#3052. I'll create an issue for the internal representations of these classes on cpython.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: other Other topics not covered
Projects
None yet
Development

No branches or pull requests

3 participants