Search code examples
pythonpython-typingtype-alias

Why does attempting to do a `Callable` alias (`Alias=Callable`) cause a "Bad number of arguments" when using it as a generic?


I've encountered the following (error in 2nd snippet) while experimenting with type hinting:

First, without any alias, everything passes the type checking:

from typing import Callable

T = TypeVar("T")  # Invariant by default
P = ParamSpec("P")

def some_decorator(f: Callable[P, T]): ...  # Passes type checking

Then, with my first attempt to use an alias:

from typing import Callable, TypeVar, ParamSpec

T = TypeVar("T")  # Invariant by default
P = ParamSpec("P")

MyCallableAlias = Callable

# error: Bad number of arguments for type alias, expected 0, given 2  [type-arg] 
def my_decorator(f: MyCallableAlias[P, T]): ...

I don't really understand this as I would have expected MyCallableAlias to behave exactly like Callable.

The solution:

The thing that seems to work is to use a Protocol:

from typing import Protocol, TypeVar, ParamSpec, Generic

# Using the default variance (invariant) causes an error at type checking.
T = TypeVar("T", covariant=True)
P = ParamSpec("P")

class MyCallableAlias(Generic[P, T], Protocol):
    def __call__(self, *args: P.args, **kwargs: P.kwargs): ...

def my_decorator(f: MyCallableAlias[P, T]): ...

Why didn't my alias work in the second example?

My solution seems over-complicated for a simple alias (actually not an alias anymore) compared to the use of what it's an alias for.

Note :

The tools and versions used for these examples were:

  • Python 3.12
  • MyPy 1.10

Solution

  • It is just how things are defined. Callable in MyCallableAlias = Callable means Callable[..., Any].

    You need to make MyCallableAlias generic too:

    (playgrounds: Mypy, Pyright)

    P = ParamSpec('P')
    R = TypeVar('R')
    
    MyCallableAlias = Callable[P, R]
    
    def some_decorator(f: MyCallableAlias[P, T]) -> MyCallableAlias[P, T]:
        ...
    

    In 3.12+, the syntax is more intuitive:

    type MyCallableAlias[**P, R] = Callable[P, R]