pythonpython-typing

Type-hinting Callable with positional/keyword only arguments without typing.Protocol


I'm wondering if it is possible to type-hint a Callable with positional- / keyword-only argument without using typing.Protocol.

For example in this snippet - how would I type-hint x correctly so that add is assignable to it:

from typing import Callable

def add(arg: int, /, *, other: str) -> str:
    return f"{arg}{other}"

x: Callable[[int, str], str] = add
# Type Error:
# `(arg: int, /, *, other: str) -> str` is not assignable to `(int, str) -> str`

Solution

  • In short no. A combination is (currently) not possible, as keyword only parameters are not possible with Callable, as it describes positional-only parameters - you need a Protocol for more specific typing. To quote the specs:

    Parameters specified using Callable are assumed to be positional-only. The Callable form provides no way to specify keyword-only parameters, variadic parameters, or default argument values. For these use cases, see the section on Callback protocols.


    A bit more on assignability; a function with standard parameters (keyword or positional) can be assigned to a function with any parameter type.
    The reverse, if you have a function that is keyword-only / positional-only it can only be assigned to a matching type, i.e. in your case you need a Protocol that reflects these parameter types exactly.

    from typing import Callable, Protocol
    
    class Standard(Protocol):
        def __call__(self, a: int) -> None: ...
    
    class KwOnly(Protocol):
        def __call__(self, *, a: int) -> None: ...
    
    class PosOnly(Protocol):
        def __call__(self, a: int, /) -> None: ...
    
    CallableType = Callable[[int], None]
    
    def func(standard: Standard, kw_only: KwOnly, callable: CallableType, pos_only: PosOnly):
        # Standard assignable to all
        f1a: KwOnly = standard  # OK
        f1b: CallableType = standard # OK
        f1c: PosOnly = standard # OK
    
        # Keyword-Only assignable to keyword-only
        f2a: Standard = kw_only  # error
        f2b: CallableType = kw_only  # error
        f2c: PosOnly = kw_only # error
    
        # CallableType and PosOnly are equivalent; only assignable to position-only/Callable
        f3a: Standard = callable  # error
        f3b: KwOnly = callable # error
        f3c: PosOnly = callable # OK - as equivalent
    
        f4a: Standard = pos_only  # error
        f4b: KwOnly = pos_only # error
        f4c: CallableType = pos_only # OK - as equivalent
    

    I am not aware that there are any plans to change extend Callable in its behavior, e.g. accept keyword-only via TypedDicts.