异常的 Python 类型提示

我有一个这样的函数:

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:

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

37744 次浏览

Type hinting can't say anything about exceptions. They are entirely out of scope for the feature. You can still document the exception in the docstring however.

From PEP 484 -- Type Hints:

Exceptions

No syntax for listing explicitly raised exceptions is proposed. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring.

Guido van Rossum has strongly opposed adding exceptions to the type hinting spec, as he doesn't want to end up in a situation where exceptions need to be checked (handled in calling code) or declared explicitly at each level.

It is usually a good idea to document the error. This means that another developer using your function will be able to handle your errors without having to read through your code.

There are good reasons for making the exception path part of the type annotations of your function, at least in certain scenarios. It just provides you more help from the type checker whenever you need to understand which exceptions the caller has to handle.

As it is out of scope of the Python typing system to indicate which exceptions a function raises (like, for instance, in Java), we need a workaround to get this. Instead of raising, we can return the exception. That way, the exception becomes part of the function signature, and the caller has to handle it, leveraging the power of the type checker.

The following code is inspired by the way exception handling is done in Rust: It provides a Result type which can either be Ok or Err. Both Ok and Err classes have an unwrap() function, which either returns the wrapped value or raises the wrapped exception.

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 is a Generic, and it takes two types: the type of the Ok value, and the type of the Err exception. Here it is applied to your example:

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()