Search code examples
pythontypeserror-handlinginstancetyping

Check typing for hybrid types


Is there a function similar to isinstance that can identify if some data is of hybrid types, for example (suppose such a function is named isinstance2):

data = [1, 2, 3]
data2 = {'a', 'b', 'c'}

isinstance2(data, list[int])  # True
isinstance2(data2, list[int]) # False
isinstance2(data2, set[str])  # True

The use case is a class that checks types during instantiation:

class foo():

   def __init__(self, data):
       if isinstance2(data, list[str]):
           # do stuff
       else:
           raise TypeError

Solution

  • If you want to generalise this kind of type-checking, you could maybe do something like the following:

    from typing import Any, Sequence, Callable, Container
    
    funcs_dict: dict[Any, Callable[[Any, Sequence[Any]], bool]] = {
        list: lambda container, args: all(
            isinstance(obj, args[0])
            for obj in container
            ),
    
        dict: lambda container, args: all(
            isinstance(key, args[0]) and isinstance(value, args[1])
            for key, value in container.items()
            ),
    
        tuple: lambda container, args: all(
            isinstance(tuple_elem, arg)
            for tuple_elem, arg in zip(container, args)
            )
    
        # etc for as many types as you want to include
        }
    
    
    def runtime_generic_type_check(container: Container, annotation: Any) -> bool:
        origin: Any = annotation.__origin__
        args: Sequence[Any] = annotation.__args__
    
        return (
            isinstance(container, origin)
            and funcs_dict[origin](container, args)
            )
    

    Examples:

    >>> int_list = [1, 2]
    >>> runtime_generic_type_check(int_list, list[int])
    True
    >>> from typing import List
    >>> runtime_generic_type_check(int_list, List[int]
    True
    >>> runtime_generic_type_check(int_list, list[str]
    False
    >>> runtime_generic_type_check(int_list, tuple[int])
    False
    

    There are important caveats to this approach, however. This will essentially only work with very simple type hints — it won't work if you have a type hint like tuple[str, ...], nor will it work for a nested container, like dict[str, dict[str, int]]. It also won't work for non-container type hints like Callable[[<parameters>], <return-type>]. You could alter the function to take account of these limitations, but the code quickly becomes much more complex. Finally, this works on my python 3.9 shell but not on my python 3.6 shell — typing.List[str].__origin__ is typing.List in python 3.6, while it is list in python 3.9. I'm not sure when that change took place.