5

Suppose I'm trying to write type hints for a library function that registers a deserializer for a user-defined type: the user should provide a type T along with a function decode: str -> T.

The most natural way I can think to write this with python's PEP-484 type hints is the following:

from typing import Callable, Type, TypeVar
T = TypeVar("T")
def register_decoder(type_: Type[T], decode: Callable[[str], T]):
    ...

Unfortunately for me, the fact that Type[T] is covariant in T means this is not quite strict enough on the decode function: at least in pyright, the invocation register_decoder(int, decode=str) passes typecheck, with the type variable T resolved as the union int | str:

pyright's inference of int|str

Is there a way to type-hint this method that enforces the constraint that decode returns instances of type_, so that this example raises an error because str does not return int? One thing that would do the job is a non-covariant equivalent of Type[T] that accepts only the exact class object T rather than any subtype, but I'm not sure anything like this exists in Python.

1 Answer 1

1

Using mypy --strict gives me the expected error. Must be something specific to the linter you are using.

from typing import Callable, Type, TypeVar
T = TypeVar("T")
def register_decoder(type_: Type[T], decode: Callable[[str], T]) -> None:
    return

register_decoder(int, str)
>mypy --strict test.py
test.py:6: error: Argument 2 to "register_decoder" has incompatible type "Type[str]"; expected "Callable[[str], int]"
Found 1 error in 1 file (checked 1 source file)

>python --version
Python 3.9.5

>mypy --version
mypy 0.910
3
  • Very interesting! I hadn't thought to try mypy - I've just been using pyright, and after delving into the documentation for Type[] I figured the issue was with my type hint, not the type checker. From my limited understanding I still feel like this is the case - isn't pyright's interpretation with T = int|str actually completely valid given that Type[T] is covariant in T? Commented Jun 28, 2022 at 2:20
  • @AnthonyCarapetis this is a good question, and to be honest I'm not sure. My guess would be that mypy does not automatically consider unions unless specified, and prefers types with contravariance. This more verbose example would work a: Union[Type[str], Type[int]] = int; register_decoder(a, str)
    – flakes
    Commented Jun 28, 2022 at 4:56
  • 1
    This is due to mypy's preference for "join" and "meet" operations over unions (github.com/python/mypy/wiki/Type-Checker#join-and-meet). This preference is a source of many, many bugs in mypy, so pyright's preference for unions is usually desirable (github.com/python/mypy/issues/12056). I would say that mypy gives a desirable type error by accident here, rather than this being a feature of mypy's type-variable solver. Commented Jun 28, 2022 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.