Search code examples
pythonmypy

How to distinguish between two identical TypeAlias of Callable in Mypy


I have two calculator types, Calculator1, Calculator2 defined as follows:

Calculator1: TypeAlias = Callable[[int], int]
Calculator2: TypeAlias = Callable[[int], int]

I would like to ensure that the type checker is able to raise error in case they are used interchangeably. For example, I define additional functionality:

def calc1(x: int) -> int:
    return x ** 2

def get_calc1() -> Calculator1:
    return calc1


def double_application(num: int, calc: Calculator2) -> int:
    return calc(calc(num))

Even when I pass in Calculator1 in double_application it doesn't raise error. I tried this using the following functionality:

def main():
    calc = get_calc1()
    val = 2
    final_val = double_application(val, calc)
    print(final_val)

main()

I am unclear on how I would be able to separate them without creating a NewType. Further, a NewType can't be defined over Callable of the follow sort. I had raised this question in a previous question (here).


Solution

  • You must use NewType for your desired semantics. To make NewType work with Callables, you need an intermediate type that is both subclassable and a Callable. See this mypy playground snippet for how I use SubclassableCallable to achieve what you need. Declarations are also below:

    @dataclass(frozen=True)
    class SubclassableCallable:
        func: Callable[[int], int]
    
        def __call__(self, x: int, /) -> int:
            return self.func(x)
    
    Calculator1 = NewType("Calculator1", SubclassableCallable)
    Calculator2 = NewType("Calculator2", SubclassableCallable)
    

    The resulting mypy error is, as you expect:

    error: Argument 2 to "double_application" has incompatible type "Calculator1"; expected "Calculator2"  [arg-type]