函数类型提示,表明该函数永远不会返回

34

Python的新类型提示功能使我们可以对函数返回None进行类型提示...

def some_func() -> None:
    pass

......或者保留返回类型未指定,根据PEP的规定,这应该导致静态分析器假定可能有任何返回类型:

没有注释的任何函数都应该被视为具有最通用的可能类型

然而,如何提示我一个函数永远不会返回?例如,如何正确地提示这两个函数的返回值类型?

def loop_forever():
    while True:
        print('This function never returns because it loops forever')

def always_explode():
    raise Exception('This function never returns because it always raises')

在这些情况下,既不指定-> None,也不保留返回类型未指定似乎都不正确。

3个回答

30

虽然问题和答案中都提到了“PEP 484 — Type Hints”标准,但是没有引用其章节:NoReturn类型正是解决你的问题的。

引用:

typing模块提供了一种特殊的类型NoReturn来注释那些永远不会正常返回的函数。例如,一个无条件抛出异常的函数:

from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')
该部分还提供了错误使用的示例。虽然它没有涵盖具有无限循环的函数,但在类型理论中,它们都同样满足由该特殊类型表达的“永远不会返回”的含义。

3
当我在2016年7月提出这个问题时,Julian和我可能会被原谅没有注意到PEP 484中的“NoReturn”类型,因为它当时还不存在,并且直到将近一年后才被添加到PEP中(https://github.com/python/peps/commit/881c6bebdb79d3a51dea0a467e81abed410e5570)。尽管如此,是的,这看起来现在是正确的答案。 - Mark Amery
2
哇!所以這個更新對於一個三年前的文件來說相當新鮮,但是它缺少像 PEP 0 中的“最後修改”標題。此外,現在它給了我一個提示,為什麼我的 IDE 還沒有識別 typing 中的 NoReturn 類型,但在運行時沒有真正的問題。 - kuza

6
从Python 3.11开始,应该使用新的底部类型typing.Never来为不返回值的函数进行类型标注。
from typing import Never

def always_explode() -> Never:
    raise

这个替代了 typing.NoReturn

...以使意图更加明确。

我猜想在某个时候,他们会弃用 NoReturn 在那个语境下,因为它们都在3.11中有效。


2
我认为你误解了文档。请注意,NoReturn文档中说:“特殊类型,表示函数永远不会返回...... NoReturn也可以用作底部类型,即没有值的类型。从Python 3.11开始,应该使用Never来表示这个概念。”- 强调是我的。文档将函数不返回和“底部类型”视为不同的“概念”,并表示您应该使用Never来表达底部类型的概念,但使用NoReturn来表达函数永远不会返回的概念。我认为不会有弃用的情况! - Mark Amery
3
@MarkAmery,整个文档都相当不清晰,但对于 Never,它明确表示“可以用来定义永远不会被调用或永远不会返回的函数”。 - joel
3
@MarkAmery 这很奇怪,因为 PRfeature request 都只将其作为底部类型进行讨论。需要澄清。 - joel
3
就我个人而言,我认为将具有相同含义的两个不同事物放在一起只会增加混淆。至少,现在我感到困惑了,但以前并没有这种感觉。 - joel
有趣的是,名称“Never”(据我所知)也指函数不返回,依赖于尴尬的转喻来解释(“此函数返回'int'”与“此函数返回'Never'”=“永远不会返回”)。因此,新名称并不比旧名称更清晰或与返回值无关。 - user3840170

2
在2016年7月,这个问题还没有答案(现在有了NoReturn;请参见新的被接受的答案)。以下是一些原因:
  • 当一个函数不返回时,没有返回值(甚至没有None)可以分配给类型。所以你实际上并不是在尝试注释一个类型;你正在尝试注释没有类型的缺失

  • 类型提示PEP刚被采用为标准,截至Python 3.5版本。此外,PEP只建议关于类型注释应该看起来像什么,同时故意模糊了如何使用它们。所以除了示例之外,没有标准告诉我们如何做任何特定的事情。

  • PEP有一部分可接受的类型提示,其中写道:

    注释必须是有效的表达式,在定义函数时不会引发异常(但下面讨论前向引用)。

    注释应该保持简单,否则静态分析工具可能无法解释这些值。例如,动态计算的类型不太可能被理解。(这是一个故意有些模糊的要求,特定的包含和排除可能会在未来版本的PEP中根据讨论被添加。)

    所以它试图阻止您做过于创造性的事情,比如在返回类型提示中抛出异常以表示函数永远不会返回。

  • 关于异常,PEP写道:

    没有提议列出明确引发的异常的语法。目前这个特性唯一已知的用例是文档化,在这种情况下,建议将此信息放在docstring中。

  • 有一个类型注释的建议,在其中你有更多的自由,但即使在那个部分中也没有讨论如何记录缺少类型。

在稍微不同的情况下,当您想暗示某个“普通”函数的参数或返回值应该是一个永远不会返回的可调用对象时,有一件事情可以尝试。 语法Callable[[ArgTypes...] ReturnType],因此您可以省略返回类型,如 Callable[[ArgTypes...]]。但是,这不符合推荐的语法,所以严格来说它不是可接受的类型提示。类型检查器可能会出现错误。
结论:您领先于时代。这可能令人失望,但对您也有好处:您仍然可以影响非返回函数应如何注释。也许这将是您参与标准化过程的借口。 :-)
我有两个建议。
  1. Allow omitting the return type in a Callable hint and allow the type of anything to be forward hinted. This would result in the following syntax:

    always_explode: Callable[[]]
    def always_explode():
        raise Exception('This function never returns because it always raises')
    
  2. Introduce a bottom type like in Haskell:

    def always_explode() -> ⊥:
        raise Exception('This function never returns because it always raises')
    
这两个建议可以结合起来。

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