Reputation: 2657
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'
Upvotes: 68
Views: 33657
Reputation: 51
As far as I know, there is no built-in solution, so I've built a nested_type_checker
package to check for a parametrized (nested) type.
To use it, install it via console:
pip install nested_type_checker
imoprt is_object_of_type
function:
from nested_type_checker import is_object_of_type
obj = [123, ({"": True},)]
CorrectType = list[int | tuple[dict[str, bool]]]
WrongType = list[bool | tuple[dict[str, bool]]]
a = is_object_of_type(obj, CorrectType)
b = is_object_of_type(obj, WrongType)
print(a) # outputs True
print(b) # outputs False
I hope it helps.
Upvotes: 1
Reputation: 793
the inspect module can resolve this pretty easily using vanilla python - no external modules required :)
It's a bit simplistic, granted; it probably won't work for deeply nested types (like dictionaries that need a given key/value type) but you might be able to expand this using the "typing" library.
import inspect
def enforce_type_annotation(fn):
parameters = inspect.signature(fn).parameters
param_keys = list(parameters.keys())
def wrapper(*args, **kwargs):
errors = list()
# -- iterate over positionals
for i in range(len(args)):
param = parameters[param_keys[i]]
value = args[i]
# -- if the parameter is not annotated, don't validate.
if not param.annotation:
continue
if not isinstance(value, param.annotation):
errors.append(
f'Positional argument {param} was given type {type(value)} but expected {param.annotation}!'
)
# -- this might throw a KeyError if an incorrect argument is provided
for key, value in kwargs.items():
param = parameters[key]
value = kwargs[key]
# -- if the parameter is not annotated, don't validate.
if not param.annotation:
continue
if not isinstance(value, param.annotation):
errors.append(
f'Keyword argument {param} was given type {type(value)} but expected {param.annotation}!'
)
if len(errors):
raise TypeError('\n'.join(errors))
return fn(*args, **kwargs)
return wrapper
@enforce_type_annotation
def foo(bar: bool, barry: str = None):
return "hello world"
# -- works - keyword arguments remain optional
print(foo(True))
# -- works - all types were passed correctly
print(foo(True, 'Hello'))
# -- does not work, keyword arguments may also be passed as positional
print(foo(True, 1))
# -- does not work, "barry" expects a string
print(foo(True, barry=1))
Upvotes: 0
Reputation: 2710
This is what I have discovered recently, basically this decorator does type checking at runtime raising exception if some type definition did not match. It can also do type checking for nested types (dict of strings, etc)
https://github.com/FelixTheC/strongtyping
Example:
from strongtyping.strong_typing import match_typing
@match_typing
def func_a(a: str, b: int, c: list):
...
func_a('1', 2, [i for i in range(5)])
# >>> True
func_a(1, 2, [i for i in range(5)])
# >>> will raise a TypeMismatch Exception
Upvotes: 7
Reputation: 4222
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])
Upvotes: 48
Reputation: 21585
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"
Upvotes: 18