【问题标题】:Typechecking inline type hints in PythonPython 中的类型检查内联类型提示
【发布时间】:2021-12-08 10:25:00
【问题描述】:

目前我正在使用typeguard.typechecked 来装饰我的函数并检查重要函数的输入和输出。

from typeguard import typechecked
@typechecked  # Gives me an error if argument/return types don't match the hints
def takes_int_makes_float(x: int) -> float:
    return float(x)

但是,如果我想检查来自我无法控制的函数的类型,并在类型不是我想要的类型时出错?

@proposed_decorator
def does_something_interesting():
    # Implicitly assert that this is the right type.
    # Note that this is valid Python code already, and works as a hint to my IDE.
    x: float = makes_float_or_tuple()
    print(x)

有没有办法做到这一点?当然可以使用 ast 使用装饰器来实现。

我知道我可以 assert isinstance 或类似的东西,但自动隐式方法会更好。

编辑:

作为对使用键入信息包装方法的建议的回应,我将分享我的实际用例。

我正在使用 torchtyping 记录并确保我的 PyTorch 张量的形状。

from torchtyping import TensorType
import torch

def interesting_reshaping_method(x: TensorType['batch', 'num_points', 'point_dim']):
    lengths: TensorType['batch', 'num_points', 1] = torch.norm(x, dim=2, keepdim=True)
    # ... do something that requires this exact shape to do what I want,
    # but will fail silently if the shape isn't what I want.

在这种情况下,我需要明确检查张量的类型,如果我使用了keepdim=False 或一些不同的dim,它的形状会有所不同。它还需要简短,以便我可以将其用作文档并在真正发生的错误处发现错误。

【问题讨论】:

  • "显式优于隐式。" 〜python的禅。
  • 那你为什么不把你想检查类型的函数做一个修饰版本呢?
  • @Copperfield 我对我想要的行为非常明确,我只是不想输入大量的样板。不在我要调用的函数周围使用装饰器的原因是因为库函数通常没有类型提示。 typechecked 装饰器的 typecheck_inline 标志之类的东西会非常清楚。

标签: python decorator type-hinting typeguards


【解决方案1】:

“没有在我要调用的函数周围使用装饰器的原因是因为库函数通常没有类型提示”

然后只需向它们添加类型提示。

至少有两种方法可以做到这一点。

第一个选项是,创建一个新函数,唯一的原因是添加你想要的类型提示,例如:

>>> from typeguard import typechecked
>>> from typing import Optional, Tuple, get_type_hints
>>> import mimetypes #our guinea pig for the example :p
>>> get_type_hints(mimetypes.guess_type)
{}
>>> @typechecked
def guess_type_bad(*a,**k) -> Tuple[str,Optional[int]]: #deliberately bad to see that it works
    return mimetypes.guess_type(*a,**k)

>>> guess_type_bad("test.txt")
('text/plain', None)
>>> guess_type_bad("test.tar.gz")
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    guess_type_bad("test.tar.gz")
  File "C:\Python39\lib\site-packages\typeguard\__init__.py", line 1019, in wrapper
    raise TypeError(*exc.args) from None
TypeError: type of the return value[1] must be one of (int, NoneType); got str instead
>>>
>>> @typechecked
def guess_type_good(*a,**k) -> Tuple[str,Optional[str]]:
    return mimetypes.guess_type(*a,**k)

>>> guess_type_good("test.tar.gz")
('application/x-tar', 'gzip')
>>>

第二种选择是直接写入他们的__annotations__特殊属性,然后装饰它

(这不适用于内置函数和/或 c 扩展,仅适用于使用纯 python 创建的函数,因此您不能对 math.cos 或 numpy.array 执行此操作)

>>> mimetypes.guess_type.__annotations__["return"]=Tuple[str,Optional[str]]
>>> guess_type = typechecked(mimetypes.guess_type)  #decoration with "@" is just syntactic sugar for this
>>> guess_type("test.tar.gz")
('application/x-tar', 'gzip')
>>> help(guess_type)
Help on function guess_type in module mimetypes:

guess_type(url, strict=True) -> Tuple[str, Optional[str]]
    Guess the type of a file based on its URL.
    
    [....]

>>>     

对于某些内联版本,似乎很简单,无法为函数动态地做到这一点: 7.2.2. Annotated assignment statements

如果一个名字在函数范围内被注解,那么这个名字是本地的 对于那个范围。注释从不评估并存储在函数中 范围。

由于这些信息在运行时丢失,没有任何具有任何黑客能力的装饰器可以知道函数内的给定变量以某种方式被注释,所以它只是静态类型检查器的一个工件,可以直接查看源代码(例如作为您的 IDE),因此您需要一个编译器来为您在代码中添加这些类型检查,如果您想要它们在运行时...如果您想要,那么您可能需要考虑是否更好地使用一种提供原生类型检查和强制机制的不同语言。

除此之外,使用我建议的解决方案之一或默认为 assertisinstance 等...看起来是唯一的方法

【讨论】:

  • 嗯,当我想向特定函数添加输入信息时,这是一种不错的方法,但我仍然认为我需要我在帖子中描述的 API。我已对其进行了更新以显示我的确切用例,以便您了解为什么它不完全符合要求。
  • 在您的编辑中,您输入“根据张量的形状做一些事情”,如果您当前的功能是一组 if 来检查形状,这听起来像是 dispacher 的工作,你可以试着把每个分支都做成自己的函数,让dispatcher根据类型提示选择路径,如果标准库里的不够用,你可以通过研究标准库里的来制作自己的跨度>
  • 不,抱歉,我不是说 if 语句。我的意思是形状需要正确才能使其余功能按预期工作。我更新了该函数中的注释以澄清。
  • 你能举一个例子来说明几个案例吗?,“但如果形状不是我想要的,就会默默地失败。”嗯,如果您想防御,那么您需要明确说明并检查,否则如果他/她因错误使用该东西而得到其他东西,则由用户承担
  • 没有找到一个好的解决方案,但感谢您的努力@Copperfield。我会给你赏金,然后我可能会将这个功能贡献给typeguard
【解决方案2】:

安装:

pip install runtime-type-checker

说明

您可以通过 check_type 函数根据类型或注释检查对象。

如果检查成功,该函数返回 None 或在错误的情况下引发 TypeError。

请注意,此函数不会递归检查例如类的属性。

from typing import List, Sequence, Optional, Mapping
from dataclasses import dataclass
from runtime_type_checker import check_type


check_type("a", str)  # OK
check_type(["a"], List[str])  # OK
check_type(["a", 1], Sequence[str])  # raises TypeError


@dataclass
class Foo:
    a: int
    b: Optional[Mapping[str, int]] = None


check_type(Foo(1), Foo)  # OK
check_type(Foo(1), int)  # raises TypeError

链接:https://pypi.org/project/runtime-type-checker/

【讨论】:

  • OP 已经使用的typeguard 有什么区别?
  • 它易于使用。在其他主题中,我认为typeguard 更好,但在这里我使用它。
  • OP 想要输入在函数内部注释的检查变量,而不是输入或输出的变量,typeguard 已经这样做了
  • 答案已编辑。请再检查一遍
  • 我认为 typeguard 也可以做到这一点,他希望检查工作在一个函数内部,用于在其中被注释的任意变量
猜你喜欢
  • 2019-05-10
  • 2021-12-25
  • 2023-01-24
  • 2020-08-04
  • 1970-01-01
  • 1970-01-01
  • 2020-01-14
  • 2020-11-30
  • 2021-10-19
相关资源
最近更新 更多