Search code examples
pythonpython-typing

How to type annotate the splat/spread function


Consider the common splat function, also known as spread, which takes a function of n arguments and wraps it in a function of one n-element tuple argument:

def splat(f: Callable[???, A]) -> Callable[[???], A]:
    def splatted(args: ???) -> A:
        return f(*args)
    return splatted

How do I annotate the type of parameter f and the return value? Is it even expressible in Python's type system?


Solution

  • Sure this is possible, but I'd advise sticking to positional-only parameters and strictly avoid keyword parameters (which won't work).

    The idea is to employ a type variable tuple (TVT) to capture the function's n (positional) parameters, then unpack the parameters (represented by the TVT) into a single concrete tuple in the transformed function. The transformed function will then only have one parameter, which is the concrete tuple.

    Demo (mypy Playground, Pyright playground):

    import collections.abc as cx
    
    class A: ...
    
    def splat[*Ts](f: cx.Callable[[*Ts], A], /) -> cx.Callable[[tuple[*Ts]], A]:
        def splatted(args: tuple[*Ts], /) -> A:
            return f(*args)
        return splatted
    
    
    @splat
    def f(a: int, b: str, /) -> A:
        return A()
    
    >>> f((1, ""))  # OK
    >>> f(1, "")  # error, expected 1 tuple argument
    >>> f((1, 1))  # error, second element of tuple should be `str`
    

    The decorator usage is for demonstration purposes only - you don't need to use this as a decorator.