Typing a Right-Side Partial Function -- Possible? Should it be?

James–

partial is implemented differently. It’s actually a class that wraps the function. Here’s the stub from typeshed:

def total_ordering(cls: type[_T]) -> type[_T]: ...
def cmp_to_key(mycmp: Callable[[_T, _T], int]) -> Callable[[_T], SupportsAllComparisons]: ...
@disjoint_base
class partial(Generic[_T]):
    @property
    def func(self) -> Callable[..., _T]: ...
    @property
    def args(self) -> tuple[Any, ...]: ...
    @property
    def keywords(self) -> dict[str, Any]: ...
    def __new__(cls, func: Callable[..., _T], /, *args: Any, **kwargs: Any) -> Self: ...
    def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ...
    def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...

The class implementation isn’t functionally “pure”, but it does benefit from being easier to introspect using its keywords, args, and func properties.

If you actually implemented partial as a true higher order function, in that it returns a function, you could type it with Python’s type system today. You could use ParamSpec and Concatenate to mostly express how the function transforms. You can only do that for arguments – not keywords – and only because ParamSpec takes the “spot” of the arguments (i.e. the left side). As you can see in Neil’s link, there’re a few related problems that face similar issues. You run into this mess anytime you try to type an argument slice or modification, especially if keywords are involved – partials, currying, decorators, function caches. My example isn’t likely to impact most pythonistas but decorators definitely do.

Neil – thanks for responding with that – it’s an interesting approach. Disappointing to see it hasn’t gotten much traction. I see there is currently some high-level interest in solving this problem with the BDFL’s endorsement of new syntax.

1 Like