Skip to content

functools.partial should use PEP612 ParamSpec (depends on #8708) #8703

Open

Description

@benbariteau

Currently, functools.partial's __call__ method takes *args: Any, **kwargs: Any and returns _T (a TypeVar for the return type of the callable the partial is wrapping). This is decent, but matching the parameter typing would make this more useful in more situations.

I can sort of see how to deal with this, although I'll admit I'm unsure how to make this generically work. I imagine you could probably deal with some number of Concatenates (maybe up to 5 or 10) to deal with most cases. It's possible fixing this requires another PEP to allow an arbitrary-length Concatenate-type pattern.

Related to python/mypy#1484

Activity

  1. AlexWaygood commented on Sep 7, 2022

    @AlexWaygood
    Member

    You can nearly get there with a combination of PEP 612 and PEP 646:

    import sys
    from types import GenericAlias
    from typing import Any, Callable, Concatenate, Generic, ParamSpec, TypeVar, TypeVarTuple, overload
    
    _P1 = ParamSpec("_P1")
    _P2 = ParamSpec("_P2")
    _T = TypeVar("_T")
    _R_co = TypeVar("_R_co", covariant=True)
    _Ts = TypeVarTuple("_Ts")
    
    class partial(Generic[_P1, _P2, _T, _R_co, *_Ts]):
        @overload
        def __new__(cls, __func: Callable[_P1, _R_co]) -> partial[_P1, _P1, Any, _R_co]: ...
        @overload
        def __new__(cls, __func: Callable[Concatenate[*_Ts, _P2], _R_co], *args: *_Ts) -> partial[Concatenate[*_Ts, _P2], _P2, Any, _R_co, *_Ts]: ...
        @overload
        def __new__(cls, __func: Callable[_P1, _R_co], *args: *_Ts, **kwargs: _T) -> partial[_P1, ..., _T, _R_co, *_Ts]: ...
        def __call__(self, *args: _P2.args, **kwargs: _P2.kwargs) -> _R_co: ...
        @property
        def func(self) -> Callable[_P1, _R_co]: ...
        @property
        def args(self) -> tuple[*_Ts]: ...
        @property
        def keywords(self) -> dict[str, _T]: ...
        if sys.version_info >= (3, 9):
            def __class_getitem__(cls, item: Any) -> GenericAlias: ...

    Mypy doesn't support PEP 646 yet, however. And even if it did, there would still be some corner cases that don't work (PyCQA/flake8-pyi#150 (comment))

  2. benbariteau commented on Sep 8, 2022

    @benbariteau
    Author

    Hmmm, I think that's probably the best we're gonna get for now. It's a strict improvement in my opinion.

  3. srittau commented on Sep 8, 2022

    @srittau
    Collaborator

    Cf. #8708

  4. changed the title [-]functools.partial should use PEP612 ParamSpec[/-] [+]functools.partial should use PEP612 ParamSpec (depends on #8708)[/+] on Sep 28, 2022
  5. andersk commented on Dec 11, 2023

    @andersk
    Contributor

    #8708 has been closed. Can this be un-deferred yet?

  6. AlexWaygood commented on Dec 11, 2023

    @AlexWaygood
    Member

    #8708 has been closed. Can this be un-deferred yet?

    No, pytype still crashes (meaning our CI would fail) if we make a class Generic over a TypeVarTuple, which is what the solution outlined in #8703 (comment) would require.

  7. Dreamsorcerer commented on Dec 11, 2023

    @Dreamsorcerer
    Contributor

    pytype still crashes

    Is that the one that has apparently been fixed a couple of days ago: google/pytype#1525 (comment)
    Or is there another issue to track?

  8. AlexWaygood commented on Dec 11, 2023

    @AlexWaygood
    Member

    Is that the one that has apparently been fixed a couple of days ago

    no

  9. erikkemperman commented on Jul 18, 2024

    @erikkemperman

    @AlexWaygood
    It would certainly be fantastic if the type annotations for partial captured something about the arguments as well the return type. But are you sure that the approach you outlined above would work, in principle? I didn't think it would be legal to unnpack a TypeVarTuple within Concatenate? I'm not sure if this is just a matter of the checkers not yet implementing it, or if there is some fundamental reason that this could not work, but currently both pyright and mypy disallow this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: deferredIssue or PR deferred until some precondition is fixed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions