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

dtcbnfnu  于 2023-05-19  发布在  Python
关注(0)|答案(4)|浏览(215)

typing模块(或任何其他模块)是否展示了在运行时对变量进行类型检查的API,类似于isinstance(),但理解typing中定义的类型类?
我想做一个类似于

from typing import List
assert isinstance([1, 'bob'], List[int]), 'Wrong type'
xmq68pz9

xmq68pz91#

我正在寻找类似的东西,并找到了库typeguard。这可以在您想要的任何地方自动执行运行时类型检查。也支持像问题中那样直接检查类型。从文件上看,

from typeguard import check_type

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

wgmfuz8q2#

typing模块中没有这样的函数,而且很可能永远不会有。
检查一个对象是否是一个类的示例--这只意味着“这个对象是由类的构造函数创建的”--是一个简单的测试一些标记的问题。
然而,检查对象是否是类型的“示例”不一定是可判定的:

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

虽然很容易检查foo的类型注解(假设它不是lambda),但根据Rice定理,检查它是否符合它们通常是不可判定的。
即使使用更简单的类型,如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"
gzszwxb4

gzszwxb43#

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

piok6c0g4#

inspect模块可以很容易地使用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))

相关问题