Typing Right-Side Partial Function (rpartial)
A Couple Caveats
- I’d classify myself as a typing enthusiast who knows just enough to be dangerous.
- I’m not suggesting Python’s type system should express every edge case—or that it even should.
- I’m not fully confident this is one of those edge cases. But it’s been an interesting one to explore.
Motivation
I’ve found funcy’s rpartial function particularly helpful over the years, and recently needed it in a project. I decided to bring it into my codebase and give it a modern refresh with proper typing.
If you’re unfamiliar, rpartial is like functools.partial, except it applies arguments from the right. It’s useful when you want to repeatedly call a function with the same secondary arguments, while varying the primary ones. Since arguments are often ordered from most to least important, I find rpartial more useful than partial in practice.
Here’s the original implementation from Alexander Schepanovski’s funcy:
def rpartial(func, *args, **kwargs):
"""Partially applies last arguments.
New keyworded arguments extend and override kwargs."""
return lambda *a, **kw: func(*(a + args), **dict(kwargs, **kw))
Usage:
def func(a, b, c): return a + b + c
rfunc = rpartial(func, 2, 3)
assert rfunc(1) == 6 # equivalent to func(1, 2, 3)
Typing this cleanly turned out to be surprisingly difficult.
What I Tried
Using Python 3.12+'s native generic syntax, I explored several approaches. Here are a few:
# The problem here is you can't use a `TypeVarTuple` with `ParamSpec`
def rpartial[Args, KwT, **P, R](
func: Callable[Concatenate[Args, KwT, P], R], /, *args: Args, **kwargs: KwT
) -> Callable[P, R]:
...
This fails because:
Concatenateonly supports positional types followed by aParamSpec- Mixing
TypeVarTuple(ideally*Args) and keyword unpacking (KwT) insideConcatenateis invalid - There’s no way to slice a
ParamSpecfrom the right
I also tried a simpler formulation:
def rpartial[**P, R](func: Callable[P, R], *args: object, **kwargs: object) -> Callable[P, R]:
...
This works at runtime but loses type precision. Then I explored a positional-only variant using TypeVarTuple:
def rpartial[*Ts, *Us, R](func: Callable[[*Ts, *Us], R], *args: *Us) -> Callable[[*Ts], R]:
...
This would be ideal—but Python doesn’t allow more than one TypeVarTuple in a type expression.
What’s Missing
We need a way to express:
“Given a function
f(*Ts, *Us, **Kw1, **Kw2), fix*Usand**Kw2, and return a callable that accepts*Tsand**Kw1.”
This would enable:
- Right-side partial application
- Keyword argument fixing from the right
- Type-safe currying and function slicing
Possible Directions
If this is a problem worth solving, here are a few ideas:
- Extend
Concatenateto supportTypeVarTuple+ keyword unpacking - Introduce a new
CallableTransformorCallableSliceconstruct - Allow slicing or partitioning of
ParamSpecandTypeVarTuple
Call for Feedback
Has anyone tackled this before? Are there known workarounds or proposals in flight? I’d love to hear from type checker maintainers, library authors, and typing enthusiasts.
For the curious, my current attempt at modernizing the original looks like this:
def rpartial[**P, R](func: Callable[P, R], *args: object, **kwargs: object) -> Callable[P, R]:
def partial_right(*fargs: P.args, **fkwargs: P.kwargs) -> R:
return func(*(fargs + args), **{**fkwargs, **kwargs}) # pyright: ignore[reportCallIssue]
return partial_right