Python类型提示与异常处理

108

我有一个函数长这样:

def check_for_errors(result):
    if 'success' in result:
        return True

    if 'error' in result:
        raise TypeError

    return False
在这个函数成功运行后,我应该得到一个bool类型的返回值,但如果出现错误,我会得到一个TypeError类型的返回值 - 这是可以接受的,因为我会在另一个函数中处理它。
我的函数第一行看起来像这样:
def check_for_errors(result: str) -> bool:

我的问题是:在我的类型提示中,我应该提到错误吗?

4个回答

132

类型提示对异常无法进行任何说明。这个特性完全超出了作用范围。但是,你仍然可以在文档字符串中记录异常信息。

来自PEP 484 -- 类型提示:

异常

目前未提出列出显式引发异常的语法。当前此特性唯一已知的用例是在文档中使用,因此建议将此信息放在文档字符串中。

Guido van Rossum强烈反对添加异常到类型提示规范,因为他不想陷入需要在每个级别显式地检查(在调用代码中处理)或声明异常的情况。


2
或者检查您是否在隐式引发人们不期望的奇怪异常的用例。 - Erik Aronesty
3
@ErikAronesty:定义“weird”。所有代码都可以引发MemoryError,大多数代码可以引发KeyboardInterrupt。一个好的语法检查器可以帮助你更好地理解异常,比类型提示更有用。 - Martijn Pieters
6
@EduardGrigoryev: NotImplemented 不是一个异常,它是一个单例对象,是一个特殊的标志值。你可能在想 NotImplementedError。异常仍然可以被返回,异常只是继承了 BaseException 的类,因此可以使用 Type[BaseException] 来类型提示接受或返回异常类的函数。 - Martijn Pieters
5
@shaunc:是的:typing.NoReturn - Martijn Pieters
1
@Tigran'sTips 我不在这里讨论编程语言的想法,请将它们带到Python想法讨论板块。 - Martijn Pieters
显示剩余3条评论

15

在某些情况下,将异常路径作为函数类型注释的一部分是有好处的。这样可以在需要了解调用者必须处理哪些异常时,从类型检查器中获得更多帮助(如果您对更深入的分析感兴趣,我写了一篇博客文章)。

由于Python类型系统无法指示函数引发的异常(例如Java),我们需要一个解决方法来解决这个问题。我们可以使用return而不是引发异常。这样,异常就成为函数签名的一部分,调用者必须处理它,利用类型检查器的强大功能。

以下代码受到 Rust 中异常处理的启发:它提供了一个 Result 类型,可以是 OkErrOkErr 都有一个 unwrap() 函数,它可以返回包装的值或引发包装的异常。
from typing import Generic, TypeVar, NoReturn


OkType = TypeVar("OkType")
ErrType = TypeVar("ErrType", bound=Exception)


class Ok(Generic[OkType]):
    def __init__(self, value: OkType) -> None:
        self._value = value

    def unwrap(self) -> OkType:
        return self._value


class Err(Generic[ErrType]):
    def __init__(self, exception: ErrType) -> None:
        self._exception = exception

    def unwrap(self) -> NoReturn:
        raise self._exception


Result = Ok[OkType] | Err[ErrType]

Result 是一个泛型,它有两个类型参数:表示 Ok 值的类型和表示 Err 异常的类型。这里将其应用于你的示例:

def check_for_errors(result: list[str]) -> Result[bool, TypeError]:
    if 'success' in result:
        return Ok(True)

    if 'error' in result:
        return Err(TypeError())

    return Ok(False)


def careful_method(result: list[str]):
    r = check_for_errors(result)  
    # Now, typechecker knows that r is `Result[bool, TypeError]`
    if isinstance(r, Err):
         # implement the error handling
    else:
         # implement the happy path

# If you do not want to handle the exception at this stage 
def careless_method(result: list[str]):
    check_for_errors(result).unwrap()

这只是一个简单的代码草图,用于演示原理。如果您考虑采用这种方法,实际上有一个更复杂的库poltergeist,我建议使用它。


我研发了一个轻量级库,非常注重类型安全,就像这个库一样:https://github.com/alexandermalyga/poltergeist - Alexander
@Jonathan Scholbach 这是一个优雅的解决方案,谢谢你的分享。 - dimButTries

11

通常情况下,记录错误是一个好主意。这意味着,使用您的函数的另一个开发人员将能够处理您的错误,而无需阅读您的代码。


-4
在Python中,您可以使用raise关键字在方法内部引发TypeError异常。
 def my_function(v1, v2):
    if not isinstance(v1, str):
       print('v1 must be str')  OR raise TypeError('type message here')
    if not isinstance(v2, int):
       print('v2 must be int')

my_function('1',1)

你可以使用raise TypeError


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