Python >=3.5:在运行时检查类型注释

问题描述 投票:0回答:5

typing
模块(或任何其他模块)是否提供 API 在运行时对变量进行类型检查,类似于
isinstance()
,但了解
typing
中定义的类型类?

我想运行类似的东西:

from typing import List
assert isinstance([1, 'bob'], List[int]), 'Wrong type'
python python-3.x type-hinting mypy python-typing
5个回答
46
投票

我正在寻找类似的东西并找到了图书馆typeguard。这可以在您想要的任何地方自动执行运行时类型检查。还支持直接检查问题中的类型。从文档中,

from typeguard import check_type

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

18
投票

typing
模块中没有这样的功能,而且很可能永远不会有。

检查一个对象是否是类的实例 - 这仅意味着“该对象是由类的构造函数创建的” - 是测试一些标记的简单问题。

但是,检查对象是否是类型的“实例”并不一定是可判定的:

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

虽然检查

foo
的类型注释很容易(假设它不是
lambda
),但根据赖斯定理,检查它是否符合它们通常是不可判定的。

即使使用更简单的类型,例如

List[int]
,测试也很容易变得效率低下,只能用于最小的玩具示例。

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

允许类型检查器以相对有效的方式执行此操作的技巧是保守:类型检查器尝试证明

foo
始终返回
int
。如果失败,它会拒绝该程序,即使该程序可能是有效的,即该函数很可能会被拒绝,尽管它是完全安全的:

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

7
投票

这是我最近发现的,基本上这个装饰器在运行时进行类型检查,如果某些类型定义不匹配,则会引发异常。它还可以对嵌套类型(字符串字典等)进行类型检查

https://github.com/FelixTheC/strongtyping

示例:

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

1
投票

检查模块可以使用 vanilla python 轻松解决这个问题 - 不需要外部模块:)

这有点简单化,这是理所当然的;它可能不适用于深层嵌套类型(例如需要给定键/值类型的字典),但您可以使用“打字”库来扩展它。

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

0
投票

据我所知,没有内置的解决方案,所以我构建了一个

nested_type_checker
包来检查参数化(嵌套)类型。 要使用它,请通过控制台安装它:

pip install nested_type_checker

导入

is_object_of_type
功能:

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

希望有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.