Mypy reports two identical typings has incompatible

Question:

I am using ParamSpec and Concatenate to type hint callables.

Here’s the code

from collections.abc import Callable
from typing import Concatenate, Generic, ParamSpec, TypeVar

T0 = TypeVar("T0")

P1 = ParamSpec("P1")
T1 = TypeVar("T1")


class TestClass(Generic[T0]):
    def __init__(self, obj: T0, method: Callable[Concatenate[T0, P1], T1]) -> None:
        self.obj = obj
        self.method = method


P2 = ParamSpec("P2")
T2 = TypeVar("T2")


def test_function(func: Callable[P2, T2], *args: P2.args, **kwargs: P2.kwargs) -> T2:
    return func(*args, **kwargs)


t = TestClass(1, lambda i: i)

print(test_function(t.method, t.obj))

And the mypy errors:

file.py:26: error: Argument 1 to "test_function" has incompatible type "Callable[[int, **P1], T1]"; expected "Callable[[int, **P1], T1]"
file.py:26: error: Argument 2 to "test_function" has incompatible type "int"; expected "[int, **P1.args]"

What I’m trying to do is storing a function that must accept T0 as first arg and passing it to a function that accepts anything

Asked By: CaptainCat

||

Answers:

The problem is that you’ve lost P1 and T1 information after __init__, it is not attached to your class. Using them in Generic solves the issue:

from collections.abc import Callable
from typing import Concatenate, Generic, ParamSpec, TypeVar

T0 = TypeVar("T0")

P1 = ParamSpec("P1")
T1 = TypeVar("T1")


class TestClass(Generic[T0, P1, T1]):
    def __init__(self, obj: T0, method: Callable[Concatenate[T0, P1], T1]) -> None:
        self.obj = obj
        self.method = method


P2 = ParamSpec("P2")
T2 = TypeVar("T2")


def test_function(func: Callable[P2, T2], *args: P2.args, **kwargs: P2.kwargs) -> T2:
    return func(*args, **kwargs)

def mth(i: int) -> int:  # Instead of lambda, to avoid unexpected Any
    return i
    
    
t = TestClass(1, mth)

print(test_function(t.method, t.obj))

(playground).

Strictly speaking, you could expect mypy to type method after resolving type vars, but it does never happen (in any context). It would lead to ambiguous resolution: should it be Callable[[int], int]? Callable[[T0], int]? Callable[[T0], T0]? Something else? (I cannot even say which of these I’d personally prefer, it is very hard to define non-ambiguous, you’ll have to keep in mind Generic/Protocol, bound typevars in Callable itself and in outer method, etc.)

Answered By: SUTerliakov
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.