检查混合类型的类型

Check typing for hybrid types

有没有类似于isinstance的函数,可以识别某些数据是否为混合类型,例如(假设这样的函数命名为isinstance2):

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

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

用例是 class 在实例化过程中检查类型:

class foo():

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

如果你想做这种运行类型的检查,你必须明确。 Python 本身没有任何内置内容(尽管可以并且可能确实存在各种库来简化此操作):

class foo():

   def __init__(self, data):
       if isinstance(data, list) and all(isintance(x, str) for x in data):
           # do stuff
       else:
           raise TypeError

如果你想推广这种类型检查,你可以做类似下面的事情:

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)
        )

示例:

>>> 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

但是,这种方法有一些重要的注意事项。这基本上只适用于非常简单的类型提示——如果你有像 tuple[str, ...] 这样的类型提示,它就不会工作,它也不会适用于像 dict[str, dict[str, int]] 这样的嵌套容器。它也不适用于 Callable[[<parameters>], <return-type>] 等非容器类型提示。您可以更改函数以考虑这些限制,但代码很快就会变得复杂得多。最后,这适用于我的 python 3.9 shell 但不适用于我的 python 3.6 shell — typing.List[str].__origin__typing.List in python 3.6,而在 python 3.9 中是 list。我不确定这种变化是什么时候发生的。