如何进行仅类型注释的类型断言?

12

我有两个函数:

def get_foo(params) -> Optional[str]
def bar(foo: str)

还有一个函数,它将这些函数链接在一起:

def f(params):
    # other stuff up here
    foo = get_foo(params)
    return bar(foo)

我知道基于函数中其他发生的事情,get_foo 的结果永远不会是 None。

当我对这个文件运行 mypy 时,当然会出现错误:

error: Argument 1 of "bar" has incompatible type "Optional[str]"; expected "str"

这是有意义的。

我可以添加一个assert foo is not None语句,但这是热路径代码,在我的测试中它会产生可测量的性能影响。 我想仅为mypy进行类型断言。我该怎么做?

编辑:我还尝试在赋值语句之后添加注释#type: str,但这也生成了类似的错误。


2
注意:如果你使用 Python 的 -O 选项(“优化”),它将在编译时删除 assert,因此不会有运行时惩罚。它所做的唯一其他更改是将 __debug__ 设置为常量,即 False 而不是 True,因此除了 assert 之外,大多数代码应该可以无变化地运行。 - ShadowRanger
1
foo: str = get_foo(params) 这样怎么样?你可能会被迫使用 # type: ignore 忽略 MyPy 错误,因为 get_foo 返回的是 Optional[str] - undefined
4个回答

15

你不会对此感到高兴。正式指定的方法,用于向静态类型检查器断言一个值具有特定的类型,是typing.cast,它是一个实际的函数,具有真正的运行时成本,我认为比你想要替换的assert更昂贵。它只是无损地返回其第二个参数,但仍带有函数调用开销。Python的类型注释系统没有设计零开销的类型断言语法。

作为替代方案,你可以使用Any作为“逃生口”。如果你用类型Any注释foo,mypy应该允许bar调用。局部变量的注释没有运行时成本,因此唯一的运行时成本是一个额外的局部变量存储和查找:

from typing import Any

def f(params):
    foo: Any = get_foo(params)
    return bar(foo)

除此之外,您最好的选择可能是使用 assert 并以 -O 标志运行Python,这会禁用断言。


5
谢谢,非常感谢您的回答。我已将其标记为被接受的答案。然而,这确实让我感觉像是 mypy 缺少了一个功能。相比之下,TypeScript 允许通过类型断言手动缩小类型范围。但是,在上面的例子中,如果你将 Any 改为 str,那么将会生成一个错误。 - Daniel Kats
这是不正确的。根据文档,"但在运行时,我们有意不检查任何内容(我们希望这尽可能快)",因此 typing.cast 是一个实际的函数,具有真正的运行时成本,我认为比您想要替换的 assert 更昂贵... - KFL
2
@KFL 虽然“cast”函数没有实际作用,但它仍然是需要查找和调用的函数。在Python中,即使是身份函数的函数调用也不是免费的。 - MisterMiyagi

2

你可以使用TYPE_CHECKING变量,在运行时为False,但在类型检查期间为True。这将避免assert的性能损失:

from typing import TYPE_CHECKING

def f(params):
    # other stuff up here
    foo = get_foo(params)
    
    if TYPE_CHECKING:
        assert foo is not None
    
    return bar(foo)


4
这仍然会对性能产生影响 - 你必须查找全局变量并在其上进行分支,这两个操作在Python中比某些人所期望的像C语言这样的语言中要昂贵得多。 - user2357112

0
如果你使用pytype作为你的类型检查器,以下内容可以正常工作:
def f(params):
    # other stuff up here
    foo = get_foo(params)  # type: str  # pytype: disable=annotation-type-mismatch
    return bar(foo)

同意,这并不漂亮,但我相信这正是你所需要的(而且公平地说,你所尝试的事情也不漂亮)。
编辑:如上所述,这适用于pytype,而不是mypy。

很不幸,这个无法通过类型检查。Mypy报错为赋值时类型不兼容(表达式类型为"Optional[str]",变量类型为"str")。顺便说一下,楼主在问题中提到这个方法不起作用。 - undefined
1
啊,确实,我忘了这是与mypy一起使用的。我正在使用pytype,它可以正常工作。不过很可能有一个等价的方式来禁用annotation-type-mismatch。 请注意,在原始帖子中,他们没有添加" # pytype: disable=annotation-type-mismatch"。 - undefined
我觉得如果你加上 # type: ignore[assignment] 这句话,这个在mypy里也能用。 - undefined

0
你可以明确地使用缩小的类型`str`来注释`foo`。
foo: str = get_foo(params)

通常情况下,这会导致mypy发出错误提示。
error: Incompatible types in assignment (expression has type "str | None", variable has type "str")  [assignment]

然而,您可以通过在赋值行上添加# type: ignore comment来消除此错误提示。
foo: str = get_foo(params)  # type: ignore

可选地,您可以通过 # type: ignore [assignment] 来缩小对代码的静态检查。

在假设 foo 是一个 str 的情况下,将对后续所有代码进行验证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接