Project Icon

result

在Python中实现Rust式结果类型的错误处理

Result库将Rust的结果类型概念引入Python,为函数提供Ok(value)或Err(error)返回选项。它通过unwrap、expect等方法简化错误处理,并引入as_result装饰器和do表示法优化代码。这个轻量级库特别适合需要清晰区分操作成功与失败的场景,为Python开发者提供了一种类型安全的错误处理新思路。

结果

GitHub Workflow Status (branch) Coverage

用于Python 3 的简单结果类型受Rust启发,完全类型注释。

安装

最新版本:

$ pip install result

最新GitHub main分支版本:

$ pip install git+https://github.com/rustedpy/result

摘要

这个想法是,结果值可以是Ok(value)Err(error),有一种方法来区分这两者。OkErr都是封装任意值的类。Result[T, E]是一个泛型类型别名,表示typing.Union[Ok[T], Err[E]]。它将改变以下代码:

def get_user_by_email(email: str) -> Tuple[Optional[User], Optional[str]]:
    """
    返回用户实例或错误消息。
    """
    if not user_exists(email):
        return None, 'User does not exist'
    if not user_active(email):
        return None, 'User is inactive'
    user = get_user(email)
    return user, None

user, reason = get_user_by_email('ueli@example.com')
if user is None:
    raise RuntimeError('Could not fetch user: %s' % reason)
else:
    do_something(user)

变为这样:

from result import Ok, Err, Result, is_ok, is_err

def get_user_by_email(email: str) -> Result[User, str]:
    """
    返回用户实例或错误消息。
    """
    if not user_exists(email):
        return Err('User does not exist')
    if not user_active(email):
        return Err('User is inactive')
    user = get_user(email)
    return Ok(user)

user_result = get_user_by_email(email)
if is_ok(user_result):
    # type(user_result.ok_value) == User
    do_something(user_result.ok_value)
else:
    # type(user_result.err_value) == str
    raise RuntimeError('Could not fetch user: %s' % user_result.err_value)

请注意,.ok_value只存在于Ok的实例上,.err_value只存在于Err的实例上。

如果您使用的是Python版本3.10或更高版本,您还可以使用优雅的match语句:

from result import Result, Ok, Err

def divide(a: int, b: int) -> Result[int, str]:
    if b == 0:
        return Err("Cannot divide by zero")
    return Ok(a // b)

values = [(10, 0), (10, 5)]
for a, b in values:
    match divide(a, b):
        case Ok(value):
            print(f"{a} // {b} == {value}")
        case Err(e):
            print(e)

并非所有方法(https://doc.rust-lang.org/std/result/enum.Result.html)都已实现,只实现了在Python上下文中有意义的方法。所有这些都在一个包中,使得更容易处理可能为"确定"或"不确定"的值,而无需求助于自定义异常。

API

自动生成的API文档也可在./docs/README.md中找到。

创建实例:

>>> from result import Ok, Err
>>> res1 = Ok('yay')
>>> res2 = Err('nay')

检查结果是否为OkErr:

if is_err(result):
    raise RuntimeError(result.err_value)
do_something(result.ok_value)

或者

if is_ok(result):
    do_something(result.ok_value)
else:
    raise RuntimeError(result.err_value)

另外,也可以使用isinstance(与类型守卫函数is_okis_err可互换使用)。但是,依赖于isinstance可能会导致代码稍微不太可读和不太简洁:

if isinstance(result, Err):
    raise RuntimeError(result.err_value)
do_something(result.ok_value)

您还可以使用OkErr类型检查对象是否为OkErr。请注意,此类型仅为方便而设计,不应用于其他任何目的。使用(Ok, Err)也可以work fine:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> isinstance(res1, OkErr)
True
>>> isinstance(res2, OkErr)
True
>>> isinstance(1, OkErr)
False
>>> isinstance(res1, (Ok, Err))
True

Result转换为值或None:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.ok()
'yay'
>>> res2.ok()
None

Result转换为错误或None:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.err()
None
>>> res2.err()
'nay'

直接访问值,无需任何其他检查:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.ok_value
'yay'
>>> res2.err_value
'nay'

请注意,这是一个属性,您无法对其进行赋值。结果是不可变的。

当值内部无关紧要时,我们建议使用Nonebool,但您可以自由使用任何您认为最合适的值。Result(OkErr)实例中必须始终包含某些内容。如果您正在寻找可能包含值的类型,您可能对maybe感兴趣。

unwrap方法在Ok时返回值,unwrap_err方法在Err时返回错误值,否则会引发UnwrapError:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.unwrap()
'yay'
>>> res2.unwrap()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\project\result\result.py", line 107, in unwrap
    return self.expect("Called `Result.unwrap()` on an `Err` value")
File "C:\project\result\result.py", line 101, in expect
    raise UnwrapError(message)
result.result.UnwrapError: Called `Result.unwrap()` on an `Err` value
>>> res1.unwrap_err()
Traceback (most recent call last):
...
>>>res2.unwrap_err()
'nay'

你可以使用expectexpect_err来显示自定义错误消息:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.expect('not ok')
'yay'
>>> res2.expect('not ok')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\project\result\result.py", line 101, in expect
    raise UnwrapError(message)
result.result.UnwrapError: not ok
>>> res1.expect_err('not err')
Traceback (most recent call last):
...
>>> res2.expect_err('not err')
'nay'

可以使用unwrap_orunwrap_or_else返回默认值:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.unwrap_or('default')
'yay'
>>> res2.unwrap_or('default')
'default'
>>> res1.unwrap_or_else(str.upper)
'yay'
>>> res2.unwrap_or_else(str.upper)
'NAY'

unwrap方法会引发UnwrapError。可以使用unwrap_or_raise方法引发自定义异常:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.unwrap_or_raise(ValueError)
'yay'
>>> res2.unwrap_or_raise(ValueError)
ValueError: nay

可以使用mapmap_ormap_or_elsemap_err对值和错误进行映射:

>>> Ok(1).map(lambda x: x + 1)
Ok(2)
>>> Err('nay').map(lambda x: x + 1)
Err('nay')
>>> Ok(1).map_or(-1, lambda x: x + 1)
2
>>> Err(1).map_or(-1, lambda x: x + 1)
-1
>>> Ok(1).map_or_else(lambda: 3, lambda x: x + 1)
2
>>> Err('nay').map_or_else(lambda: 3, lambda x: x + 1)
3
>>> Ok(1).map_err(lambda x: x + 1)
Ok(1)
>>> Err(1).map_err(lambda x: x + 1)
Err(2)

为了节省内存,OkErr类都是'slotted',即它们定义了__slots__。这意味着为实例分配任意属性将引发AttributeError

as_result装饰器

as_result()装饰器可用于通过指定一个或多个异常类型快速将"普通"函数转换为Result返回函数:

@as_result(ValueError, IndexError)
def f(value: int) -> int:
    if value == 0:
        raise ValueError  # becomes Err
    elif value == 1:
        raise IndexError  # becomes Err
    elif value == 2:
        raise KeyError  # raises Exception
    else:
        return value  # becomes Ok

res = f(0)  # Err[ValueError()]
res = f(1)  # Err[IndexError()]
res = f(2)  # raises KeyError
res = f(3)  # Ok[3]

可以指定Exception(甚至BaseException)以创建一个"catch all"Result返回类型。这实际上与try后跟except Exception相同,在大多数情况下不被视为良好实践,因此需要显式选择。

由于as_result是一个常规装饰器,它可以用于包装现有函数(包括来自其他库的函数),尽管语法略有不同寻常(没有通常的@):

import third_party

x = third_party.do_something(...)  # 可能会引发异常,谁知道呢?

safe_do_something = as_result(Exception)(third_party.do_something)

res = safe_do_something(...)  # Ok(...) or Err(...)
if is_ok(res):
    print(res.ok_value)

Do notation

语法糖是用于一系列and_then()调用的记号记法。 和Rust或Haskell中的等价物类似,但语法有所不同。 与其写x <- Ok(1),不如写for x in Ok(1)。因为语法基于生成器,最终结果必须是第一行,而不是最后一行。

final_result: Result[int, str] = do(
    Ok(x + y)
    for x in Ok(1)
    for y in Ok(2)
)

注意,如果你省略了类型注解, final_result: Result[float, int] = ...,你的类型检查器可能无法推断返回类型。为了避免类型检查器的错误或警告,在使用do函数时应添加类型提示。

这与Rust的m!宏类似:

use do_notation::m;
let r = m! {
    x <- Some(1);
    y <- Some(2);
    Some(x + y)
};

注意,如果你的do语句有多个for,你可以访问前一个for中绑定的标识符。例如:

my_result: Result[int, str] = do(
    f(x, y, z)
    for x in get_x()
    for y in calculate_y_from_x(x)
    for z in calculate_z_from_x_y(x, y)
)

你可以如下使用do()配合awaited值:

async def process_data(data) -> Result[int, str]:
    res1 = await get_result_1(data)
    res2 = await get_result_2(data)
    return do(
        Ok(x + y)
        for x in res1
        for y in res2
    )

但是,如果你想在表达式中await某些东西,请使用do_async():

async def process_data(data) -> Result[int, str]:
    return do_async(
        Ok(x + y)
        for x in await get_result_1(data)
        for y in await get_result_2(data)
    )

故障排除do()调用:

TypeError("Got async_generator but expected generator")

有时常规的do()可以处理async值,但这个错误意味着你遇到了一种它无法处理的情况。这种情况下你应该使用do_async()

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号