34
votes

Does the typing module (or any other module) exhibit an API to typecheck a variable at runtime, similar to isinstance() but understanding the type classes defined in typing?

I'd like to be to run something akin to:

from typing import List
assert isinstance([1, 'bob'], List[int]), 'Wrong type'
2
No you can't, but there's a similar question here: stackoverflow.com/questions/37973820/…, and I tried to answer it. - max
@max Thanks for the attempt. I actually tried getting in touch with the mypy people through gitter and it seems like a similar feature is in the works, I'll see if I can get someone from the project to answer here and maybe update it as it moves forward. - Bertrand Caron
As I understand it, typing_inspect is not exactly what you are looking for; it's more about inspecting the types objects themselves. - Elazar
There's a very elaborate type checking implementation in this closely related question: Validating detailed types in python dataclasses - Aran-Fey
Thanks for that, seems like there is enough code in the answer to warrant a small (but very useful) package ;) - Bertrand Caron

2 Answers

19
votes

I was looking for something similar and found the library typeguard. This can automatically do runtime type checks wherever you want. Checking types directly as in the question is also supported. From the docs,

from typeguard import check_type

# Raises TypeError if there's a problem
check_type('variablename', [1234], List[int])
14
votes

There is no such function in the typing module, and most likely there won't ever be.

Checking whether an object is an instance of a class - which only means "this object was created by the class' constructor" - is a simple matter of testing some tagging.

However, checking whether an object is an "instance" of a type is not necessarily decidable:

assert isinstance(foo, Callable[[int], str]), 'Wrong type'

Although it is easy to inspect the typing annotations of foo (assuming it's not a lambda), checking whether it complies to them is generally undecidable, by Rice's theorem.

Even with simpler types, such as List[int] the test will easily become far too inefficient to be used for anything but the smallest toy examples.

xs = set(range(10000))
xs.add("a")
xs.pop()
assert isinstance(xs, Set[int]), 'Wrong type'

The trick that allows type checker to perform this operation in a relatively efficient way, is to be conservative: the type checker tries to prove that foo always return int. If it fails, it rejects the program, even though the program may be valid, i.e. this function is likely to be rejected, although it is perfectly safe:

def foo() -> int:
    if "a".startswith("a"):
        return 1
    return "x"