Skip to content

Feature - Partial Type Specifications For Callable #8263

@rmorshea

Description

@rmorshea

Feature Request

It would be awesome if it were possible to create a partial type spec for Callable. For example, I might want to be able to specify that the first argument of a function must be an integer, but that any other parameters are allowed. This is useful if parameters are being curried to a function as in the decorator cast_first_arg decorator below where the only requirement of func is that it's first argument is an int:

@cast_first_arg(int)  
def func(x: int, y: str) -> Any:
    # First positional argument is always cast to an integer.
    # All other parameters are passed through without modification.
    ...

At the moment, the only alternative to a "partial callable type spec" is to use VarArg and KwArg, however this is problematic since, in the case of cast_first_arg, the function doesn't actually need to accept variadic positional or keyword arguments.

Proposed Syntax

To provide a partial argument specification one should use ... before, between, or after any extended callable types. Using this syntax the type spec for cast_first_arg might look like this:

T = TypeVar("T", bound=Type)
F = Callable[[Var(int), ...], Any]

def cast_first_arg(cls: T) -> Callable[F, Callable[..., Any]]:
    def decorator(func: F) -> Callable[..., Any]:
        def wrapper(first: Any, *args: Any, **kwargs: Any): Any:
            return func(cls(first), *args, **kwargs)
       return wrapper
    return decorator

Ellipsis After

When ... follows some number of argument specifications should indicate that any other function parameters not explicitly indicated therein is allowed.

FirstArgIsInt = Callable[[Arg(int), ...], Any]

def function(a: int, b: str) -> Any: ...    # 'b' is an unspecified, but allowe

f: FirstArgIsInt = function

Ellipsis Before

Similarly if ... is placed before some number of argument specifications then only the last argument will have been specifically defined:

LastArgIsInt = Callable[[..., Arg(int)], Any]

def function(b: str, a: int) -> Any: ...    # 'b' is an unspecified, but allowed

f: LastArgIsInt = function

Ellipsis Between

Lastly ... placed between argument specifications should indicate that any parameters between the two explicitly defined parameters are allowed:

FirstAndLastAreInt = Callable[[Arg(int), ..., Arg(int)], Any]

def function(a: int, b: str, c: int) -> Any: ...    # 'b' is an unspecified, but allowed

f: FirstAndLastAreInt = function

Multiple Ellipsis

This could add complications and edge cases to the feature implementation so perhaps this could be left for a later iteration of partial callable type specifications, however this syntax enables you to describe an even wider variety of functions:

HasArgNamedC = Callable[[..., Arg(Any, "c"), ...], Any]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions