结果
用于Python 3 的简单结果类型受Rust启发,完全类型注释。
安装
最新版本:
$ pip install result
最新GitHub main
分支版本:
$ pip install git+https://github.com/rustedpy/result
摘要
这个想法是,结果值可以是Ok(value)
或Err(error)
,有一种方法来区分这两者。Ok
和Err
都是封装任意值的类。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')
检查结果是否为Ok
或Err
:
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_ok
和is_err
可互换使用)。但是,依赖于isinstance
可能会导致代码稍微不太可读和不太简洁:
if isinstance(result, Err):
raise RuntimeError(result.err_value)
do_something(result.ok_value)
您还可以使用OkErr
类型检查对象是否为Ok
或Err
。请注意,此类型仅为方便而设计,不应用于其他任何目的。使用(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'
请注意,这是一个属性,您无法对其进行赋值。结果是不可变的。
当值内部无关紧要时,我们建议使用None
或bool
,但您可以自由使用任何您认为最合适的值。Result
(Ok
或Err
)实例中必须始终包含某些内容。如果您正在寻找可能包含值的类型,您可能对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'
你可以使用expect
和expect_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_or
或unwrap_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
可以使用map
、map_or
、map_or_else
和map_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)
为了节省内存,Ok
和Err
类都是'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()
。