I have to check an object that may have been created by an API.
When I try using isinstance(obj, MyClass)
it get a TypeError
if obj
was created by the API.
I wrote a custom function to handle this.
def is_instance(obj: Any, class_or_tuple: Any) -> bool:
try:
return isinstance(obj, class_or_tuple)
except TypeError:
return False
The issue I am having is using is_instance()
instead of the builtin isinstance()
does not have any TypeGuard support, so the type checker complains.
def my_process(api_obj: int | str) -> None:
if is_instance(api_obj, int):
process_int(api_obj)
else:
process_str(api_obj)
"Type int | str cannot be assigned to parameter ..."
How could I create a TypeGuard for this function?
You can annotate is_instance
with a TypeGuard
that narrows the type to that of the second argument. To handle a tuple of types or such tuples as class_or_tuple
, use a type alias that allows either a type or a tuple of the type alias itself:
T = TypeVar('T')
ClassInfo: TypeAlias = type[T] | tuple['ClassInfo', ...]
def is_instance(obj: Any, class_or_tuple: ClassInfo) -> TypeGuard[T]:
try:
return isinstance(obj, class_or_tuple)
except TypeError:
return False
But then, as @user2357112 points out in the comment, TypeGuard
isn't just meant for narrowing by type, but also value, so failing a check of is_instance(api_obj, int)
doesn't mean to the type checker that api_obj
is necessarily str
, so using an else
clause would not work:
def my_process(api_obj: int | str) -> None:
if is_instance(api_obj, int):
process_int(api_obj)
else:
# mypy complains: Argument 1 to "process_str" has incompatible type "int | str"; expected "str" [arg-type]
process_str(api_obj)
so in this case you would have to work around it with a redundant call of is_instance(api_obj, str)
:
def my_process(api_obj: int | str) -> None:
if is_instance(api_obj, int):
process_int(api_obj)
elif is_instance(api_obj, str):
process_str(api_obj)
Demo with mypy: https://mypy-play.net/?mypy=latest&python=3.12&gist=4cea456751dff62c3e0bc998b74462f5
Demo of type narrowing with a tuple of types with mypy: https://mypy-play.net/?mypy=latest&python=3.12&gist=98ca0795a315e541c4b1b9376d81812f
EDIT: For PyRight as you requested in the comment, you would have to make the type alias generic:
T = TypeVar('T')
ClassInfo: TypeAlias = Union[Type[T] , Tuple['ClassInfo[T]', ...]]
def is_instance(obj: Any, class_or_tuple: ClassInfo[T]) -> TypeGuard[T]:
try:
return isinstance(obj, class_or_tuple)
except TypeError:
return False
This would make mypy complain, however, so it's really down to different type checkers having different interpretations to the Python typing rules.
Demo with PyRight here