跳转到主要内容

Rust风格的类型安全结果类型

项目描述

safetywrap

Build Status coverage report

适用于Python值的完全类型安全的Rust风格包装类型

摘要

此库提供了两种主要的包装:ResultOption。这些类型允许您指定类型安全的代码,有效地处理错误或缺失的数据,而无需使用深度嵌套的if语句和大量的try-except块。

这是通过允许您在某种量子叠加中对OptionResult进行操作来实现的,其中Option可以是SomeNothing,而Result可以是OkErr。在任一情况下,类型上的所有方法都工作相同,使您能够优雅地处理两种情况。

Result[T, E]可以是Ok[T]Err[E]的实例,而Option[T]可以是Some[T]Nothing的实例。无论如何,您都可以将它们视为相同,直到您真正需要获取包装的值。

因此,而不是这样

for something in "value", None:
    if something is not None:
        val = something.upper()
    else:
        val = "DEFAULT"
    print(val)

您可以这样操作

for something in Some("value"), Nothing():
    print(something.map(str.upper).unwrap_or("DEFAULT"))

而不是这样

for jsondata in '{"value": "myvalue"}', '{badjson':
    try:
        config = capitalize_keys(json.loads(jsondata))
    except Exception:
        config = get_default_config()
    print(config["value"])

您可以这样操作

for jsondata in '{"value": "myvalue"}', '{badjson':
    print(
        Result.of(json.loads, jsondata)
        .map(capitalize_keys)
        .unwrap_or_else(get_default_config)["value"]
    )

这两个示例展示了如何使用这些类型安全的包装器使编写和理解更加容易。有关更多内容,请参阅示例部分,以及用法部分以获取提供的完整功能集。

这些类型在很大程度上受到Rust中的ResultOption类型的影响。

为mypy或您喜欢的Python类型检查器提供了详尽的类型规范,因此您可以装饰函数的输入和输出,使其返回ResultOption类型,并在提供参数或传递返回值时获得有用的反馈。

赞助

该项目由我的雇主Bestow, Inc.开发和慷慨赞助。在Bestow,我们致力于通过提供简单、便捷的保险,在线五分钟内即可购买,无需医生就诊和繁琐的手续,来实现人寿保险的民主化。

我们几乎一直在招聘优秀的开发者,如果您想加入我们,请查看我们的职业页面

目录

示例

通常,这些示例从简单到复杂。有关完整的API规范,请参阅下方的用法

通过值获取枚举成员,返回成员或None

import typing as t
from enum import Enum

from result_types import Option, Result, Some

T = t.TypeVar("T", bound=Enum)

def enum_member_for_val(enum: t.Type[T], value: t.Any) -> t.Optional[t.Any]:
    """Return Some(enum_member) or Nothing()."""
    # Enums throw a `ValueError` if the value isn't present, so
    # we'll either have `Ok(enum_member)` or `Err(ValueError)`.
    # We unwrap and return the member if it's Ok, otherwise, we just
    # return None
    return Result.of(enum, value).unwrap_or(None)

通过值获取枚举成员,返回Option

import typing as t
from enum import Enum

from result_types import Option, Result, Some

T = t.TypeVar("T", bound=Enum)

def enum_member_for_val(enum: t.Type[T], value: t.Any) -> Option[T]:
    """Return Some(enum_member) or Nothing()."""
    # Enums throw a `ValueError` if the value isn't present, so
    # we'll either have `Ok(enum_member)` or `Err(ValueError)`.
    # Calling `ok()` on a `Result` returns an `Option`
    return Result.of(enum, value).ok()

使用默认值序列化可能缺少键的字典

import json
from result_types import Result

def serialize(data: t.Dict[str, t.Union[int, str, float]]) -> str:
    """Serialize the data.

    Absent keys are "[absent]", rather than null. This allows us to maintain
    information about whether a key was present or actually set to None.
    """
    keys = ("first", "second", "third", "fourth")
    # We can even use Result to catch any JSON serialization errors, so that
    # this function will _always_ return a string!
    Result.of(
        json.dumps,
        # Result.of() will intercept the KeyError and return an Err. We use
        # `unwrap_or()` to discard the error and return the "[absent]" string
        # instead; if the key was present, the Result was Ok, and we just
        # return that value.
        {k: Result.of(lambda: data[k]).unwrap_or("[absent]") for k in keys}
    ).unwrap_or("Could not serialize JSON from data!")

发起HTTP请求,如果状态码是200,则将正文转换为JSON并返回data键。如果出现错误或data键不存在,则返回错误字符串

from functools import partial

import requests
from requests import Response
from result_types import Option, Result


def get_data(url: str) -> str:
    """Get the data!"""
    # We need to do manual type assignment sometimes when the code
    # we're wrapping does not provide types.
    # If the wrapped function raises any Exception, `res` will be
    # Err(Exception). Otherwise it will be `Ok(Response)`.
    res: Result[Response, Exception] = Result.of(requests.get, url)
    return (
        # We start as a `Result[Response, Exception]`
        res
        # And if we were an Err, map to a `Result[Response, str]`
        .map_err(str)
        # If we were Ok, and_then (aka flatmap) to a new `Result[Response, str]`
        .and_then(lambda res: (
            # Our return value starts as a `Result[Response, Response]`
            Result.ok_if(lambda r: r.status_code == 200, res).map_err(
                # So we map it to a `Result[Response, str]`
                lambda r: str(f"Bad status code: {r.status_code}")
            )
        ))
        # We are now a `Result[Response, str]`, where we are only Ok if
        # our status code was 200.
        # Now we transition to a `Result[dict, str]`
        .and_then(lambda res: Result.of(res.json).map_err(str))
        # And to a `Result[Option[str], str]`
        .map(lambda js: Option.of(js.get("data")).map(str))
        # And to a `Result[str, str]`
        .and_then(lambda data: data.ok_or("No data key in JSON!"))
        # If we are an error, convert us to an Ok with the error string
        .or_else(Ok)
        # And now we get either the Ok string or the Err string!
        .unwrap()
    )

用法

Result[T, E]

Result表示可能处于Ok状态或Err状态的一些值。

Result构造函数

Ok

Ok(value: T) -> Result[T, E]

直接使用值构造一个Ok Result。

示例

def check_value_not_negative(val: int) -> Result[int, str]:
    """Check that a value is not negative, or return an Err."""
    if val >= 0:
        return Ok(val)
    return Err(f"{val} is negative!")
Err

Err(value: E) -> Result[T, E]

直接使用值构造一个Err Result。

示例

def check_value_not_negative(val: int) -> Result[int, str]:
    """Check that a value is not negative, or return an Err."""
    if val >= 0:
        return Ok(val)
    return Err(f"{val} is negative!")
Result.of

Result.of(fn: Callable[..., T], *args: t.Any, catch: t.Type[E], **kwargs) -> Result[T, E]

使用提供的参数调用函数。如果没有抛出错误,则返回Ok(result)。否则,返回Err(exception)。默认情况下,会捕获Exception,但也可以通过catch关键字参数提供不同的错误类型。

E的类型必须是Exception或其子类之一。

此构造函数旨在在包装其他API、内置函数等时非常有用。

请注意,由于mypy中的一个bug(参见https://github.com/python/mypy/issues/3737),有时您需要显式指定catch关键字参数,即使将其设置为默认值(Exception)。这种情况并不总是发生,但发生时,您会看到mypy认为Result的类型是Result[SomeType, <nothing>]

示例

import json

def parse_json(string: str) -> Result[dict, Exception]:
    """Parse a JSON object into a dict."""
    return Result.of(json.loads, string)
Result.collect

Result.collect(iterable: Iterable[T, E]) -> Result[Tuple[T, ...], E]

将一系列结果转换为单个结果。如果所有结果都是Ok,则Ok值将被收集到最终结果的元组中。如果任何结果是Err,则直接返回Err结果。

示例

assert Result.collect([Ok(1), Ok(2), Ok(3)]) == Ok((1, 2, 3))
assert Result.collect([Ok(1), Err("no"), Ok(3)]) == Err("no")
Result.err_if

Result.err_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]

对某个值运行一个谓词,如果谓词返回True,则返回Err(val),如果谓词返回False,则返回Ok(val)

示例

from requests import Response

def checked_response(response: Response) -> Result[Response, Response]:
    """Try to get a response from the server."""
    return Result.err_if(lambda r: r.status_code >= 300, response)
Result.ok_if

Result.ok_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]

对某个值运行一个谓词,如果谓词返回True,则返回Ok(val),如果谓词返回False,则返回Err(val)

示例

def checked_data(data: dict) -> Result[dict, dict]:
    """Check if data has expected keys."""
    expected_keys = ("one", "two", "three")
    return Result.ok_if(lambda d: all(k in d for k in expected_keys), data)

Result方法

Result.and_

Result.and_(self, res: Result[U, E]) -> Result[U, E]

如果此Result是Ok,则返回res。如果此结果为Err,则返回此Result。这可以在遇到第一个错误时截断Result链。

示例

assert Ok(5).and_(Ok(6)) == Ok(6)
assert Err(1).and_(Ok(6)) == Err(1)
assert Err(1).and_(Err(2)).and_(Ok(5)) == Err(1)
assert Ok(5).and_(Err(1)).and_(Ok(6)) == Err(1)
Result.or_

Result.or_(self, res: Result[T, F]) -> Result[T, F]

如果此Result是Err,则返回res。否则,返回此Result。

示例

assert Err(1).or_(Ok(5)) == Ok(5)
assert Err(1).or_(Err(2)) == Err(2)
assert Ok(5).or_(Ok(6)) == Ok(5)
assert Ok(5).or_(Err(1)) == Ok(5)
Result.and_then

Result.and_then(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]

如果此Result是Ok,则使用此Result的包装值调用提供的函数,并返回该函数的结果。这允许轻松地将多个生成Result的调用链接在一起以生成最终Result。此方法是对Result.flatmap的别名

示例

assert Ok(5).and_then(lambda val: Ok(val + 1)) == Ok(6)
assert Err(1).and_then(lambda val: Ok(val + 1)) == Err(1)
Result.flatmap

Result.flatmap(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]

如果此Result是Ok,则使用此Result的包装值调用提供的函数,并返回该函数的结果。这允许轻松地将多个生成Result的调用链接在一起以生成最终Result。此方法是对Result.and_then的别名

示例

assert Ok(5).flatmap(lambda val: Ok(val + 1)) == Ok(6)
assert Err(1).flatmap(lambda val: Ok(val + 1)) == Err(1)
Result.or_else

Result.or_else(self, fn: t.Callable[[E], Result[T, F]]) -> Result[T, F])

如果此结果为Err,则使用此Result的包装错误值调用提供的函数,并返回该函数的结果。这允许以仍然返回最终Result的方式轻松处理潜在的错误。

示例

assert Ok(5).or_else(Ok) == Ok(5)
assert Err(1).or_else(Ok) == Ok(1)
Result.err

Result.err(self) -> Option[E]

将此Result转换为Option,如果此Result是Err,则返回Some(err_val),如果此Result是Ok,则返回Nothing()。

示例

assert Ok(5).err() == Nothing()
assert Err(1).err() == Some(1)
Result.ok

Result.ok(self) -> Option[T]

将此Result转换为Option,如果此Result是Ok,则返回Some(val),如果此结果为Err,则返回Nothing()。

示例

assert Ok(5).ok() == Some(5)
assert Err(1).ok() == Nothing()
Result.expect

Result.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T

如果此Result是Ok,则返回包装的值。否则,引发一个错误,使用提供的消息和字符串化的错误值实例化。默认情况下,引发一个RuntimeError,但可以使用exc_cls关键字参数提供一个替代错误。此方法是对Result.raise_if_err的别名。

示例

import pytest

with pytest.raises(RuntimeError) as exc:
    Err(5).expect("Bad value")
    assert str(exc.value) == "Bad value: 5"

assert Ok(1).expect("Bad value") == 1
Result.raise_if_err

Result.raise_if_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T

如果此Result是Ok,则返回包装的值。否则,引发一个错误,使用提供的消息和字符串化的错误值实例化。默认情况下,引发一个RuntimeError,但可以使用exc_cls关键字参数提供一个替代错误。此方法是对Result.expect的别名。

示例

import pytest

with pytest.raises(RuntimeError) as exc:
    Err(5).raise_if_err("Bad value")
    assert str(exc.value) == "Bad value: 5"

assert Ok(1).raise_if_err("Bad value") == 1
Result.expect_err

Result.expect_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> E

如果这个Result是Err,则返回包装的值。否则,抛出一个错误,错误实例化提供的信息和Ok值的字符串表示。默认情况下,抛出RuntimeError,但可以使用exc_cls关键字参数提供替代错误。

示例

import pytest

with pytest.raises(RuntimeError) as exc:
    Ok(5).expect_err("Unexpected good value")
    assert str(exc.value) == "Unexpected good value: 5"

assert Err(1).expect_err("Unexpected good value") == 1
Result.is_err

Result.is_err(self) -> bool

如果这个Result是Err,则返回True,如果这个Result是Ok,则返回False。

示例

assert Err(1).is_err() is True
assert Ok(1).is_err() is False
Result.is_ok

Result.is_ok(self) -> bool

如果这个Result是Ok,则返回True,如果这个Result是Err,则返回False。

示例

assert Ok(1).is_err() is True
assert Err(1).is_err() is False
Result.iter

Result.iter(self) -> Iterator[T]

如果这个Result是Ok,则返回一个长度为1的迭代器,遍历包装的值。否则,返回一个0长度的迭代器。

示例

assert tuple(Ok(1).iter()) == (1,)
assert tuple(Err(1).iter()) == ()
Result.map

Result.map(self, fn: t.Callable[[T], U]) -> Result[U, E]

如果这个Result是Ok,则将提供的函数应用于包装的值,并返回一个包含函数结果的新Ok Result。如果这个Result是Err,则不应用函数并返回此Result不变。

警告:在应用提供的函数时不会进行错误检查,并且不会捕获应用函数时抛出的异常。如果您需要带有错误处理的映射,请考虑使用与Result.of构造函数一起使用的and_then(也称为flatmap),例如:assert Ok(0).and_then(partial(Result.of, lambda i: 10 / i)) == Err(ZeroDivisionError('division by zero'))

示例

assert Ok(1).map(str) == Ok("1")
assert Err(1).map(str) == Err(1)
Result.map_err

Result.map_err(self, fn: t.Callable[[E], F]) -> Result[T, F]

如果这个Result是Err,则将提供的函数应用于包装的值,并返回一个包含函数结果的新Err Result。如果这个Result是Ok,则不应用函数并返回此Result不变。

警告:在应用提供的函数时不会进行错误检查,并且不会捕获应用函数时抛出的异常。

示例

assert Err(1).map_err(lambda i: i + 1) == Err(2)
assert Ok(1).map_err(lambda i: i + 1) == Ok(1)
Result.unwrap

Result.unwrap(self) -> T

如果这个Result是Ok,则返回包装的值。如果这个Result是Err,则抛出RuntimeError

示例

import pytest

assert Ok(1).unwrap() == 1

with pytest.raises(RuntimeError):
    Err(1).unwrap()
Result.unwrap_err

Result.unwrap_err(self) -> E

如果这个Result是Err,则返回包装的值。如果这个Result是Ok,则抛出RuntimeError

示例

import pytest

assert Err(1).unwrap() == 1

with pytest.raises(RuntimeError):
    Ok(1).unwrap()
Result.unwrap_or

Result.unwrap_or(self, alternative: U) -> t.Union[T, U]

如果这个Result是Ok,则返回包装的值。如果这个Result是Err,则返回提供的替代值。

示例

assert Ok(1).unwrap_or(5) == 1
assert Err(1).unwrap_or(5) == 5
Result.unwrap_or_else

Result.unwrap_or_else(self, fn: t.Callable[[E], U]) -> t.Union[T, U]

如果这个Result是Ok,则返回包装的值。如果这个Result是Err,则调用提供的函数,该函数以包装的错误值作为参数,并返回结果。

示例

assert Ok(1).unwrap_or_else(str) == 1
assert Err(1).unwrap_or_else(str) == "1"

Result魔术方法

Result.iter

Result.__iter__(self) -> t.Iterator[T]

实现迭代协议,允许遍历Result.iter的结果。如果这个Result是Ok,则返回一个包含包装值的长度为1的迭代器。如果这个Result是Err,则返回一个0长度的迭代器。

示例

# Can be passed to methods that take iterators
assert tuple(Ok(1)) == (1,)
assert tuple(Err(1)) == ()

# Can be used in `for in` constructs, including comprehensions
assert [val for val in Ok(5)] == [5]
assert [val for val in Err(5)] == []


# More for-in usage.
for val in Ok(5):
    pass
assert val == 5

val = None
for val in Err(1):
    pass
assert val is None
Result.eq

Result.__eq__(self, other: Any) -> bool

启用使用==的相等性检查。

将Result与other进行比较。如果other是具有相同包装值的相同类型的Result,则返回True。否则,返回False。

示例

assert (Ok(5) == Ok(5)) is True
assert (Ok(5) == Ok(6)) is False
assert (Ok(5) == Err(5)) is False
assert (Ok(5) == 5) is False
Result.ne

Result.__ne__(self, other: Any) -> bool

启用使用!=的不等性检查。

将Result与other进行比较。如果other是具有相同包装值的相同类型的Result,则返回False。否则,返回True。

示例

assert (Ok(5) != Ok(5)) is False
assert (Ok(5) != Ok(6)) is True
assert (Ok(5) != Err(5)) is True
assert (Ok(5) != 5) is True
Result.str

Result.__str__(self) -> str

启用使用str()的有用的字符串表示。

示例

assert str(Ok(5)) == "Ok(5)"
assert str(Err(5)) == "Err(5)"
Result.repr

Result.__repr__(self) -> str

启用使用repr()的有用的字符串表示。

示例

assert repr(Ok(5)) == "Ok(5)"
assert repr(Err(5)) == "Err(5)"

Option[T]

Option表示Some值或Nothing

Option构造函数

Some

Some(value: T) -> Option[T]

直接使用值构造一个Some Option。

示例

def file_contents(path: str) -> Option[str]:
    """Return the file contents or Nothing."""
    try:
        with open(path) as f:
            return Some(f.read())
    except IOError:
        return Nothing()
Nothing

Nothing() -> Option[T]

直接使用值构造一个Nothing Option。

注意:作为实现细节,Nothing 被实现为一个单例,以避免在第一次之后创建任何 Nothing 时的实例化时间。然而,由于这是一个实现细节,Nothing 选项仍应使用 == 而不是 is 进行比较。

示例

def file_contents(path: str) -> Option[str]:
    """Return the file contents or Nothing."""
    try:
        with open(path) as f:
            return Some(f.read())
    except IOError:
        return Nothing()
Option.of

Option.of(value: t.Optional[T]) -> Option[T]

将可选值转换为 Option。如果值不是 None,则返回 Some(value)。否则,如果值是 None,则返回 Nothing()

示例

assert Option.of(None) == Nothing()
assert Option.of({}.get("a")) == Nothing()
assert Option.of("a") == Some("a")
assert Option.of({"a": "b"}) == Some("b")
Option.nothing_if

Option.nothing_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]

使用提供的值调用提供的谓词函数。如果谓词返回 True,则返回 Nothing()。如果谓词返回 False,则返回 Some(value)

示例

assert Option.nothing_if(lambda val: val.startswith("_"), "_private") == Nothing()
assert Option.nothing_if(lambda val: val.startswith("_"), "public") == Some("public")
Option.some_if

Option.some_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]

使用提供的值调用提供的谓词函数。如果谓词返回 True,则返回 Some(value)。如果谓词返回 False,则返回 Nothing()

示例

assert Option.some_if(bool, [1, 2, 3]) == Some([1, 2, 3])
assert Option.some_if(bool, []) == Nothing()
Option.collect

Option.collect(options: t.Iterable[Option[T]]) -> Option[t.Tuple[T, ...]]

将一系列 Option 收集到单个 Option 中。

如果所有选项都是 Some[T],则结果是 Some[Tuple[T, ...j]]。如果有任何选项是 Nothing,则结果是 Nothing

示例

assert Option.collect([Some(1), Some(2), Some(3)]) == Some((1, 2, 3))
assert Option.collect([Some(1), Nothing(), Some(3)]) == Nothing()

Option方法

Option.and_

Option.and_(alternative: Option[U]) -> Option[U]

如果此 Option 是 Nothing,则返回其未更改。否则,如果此 Option 是 Some,则返回提供的 alternative Option。

示例

assert Some(1).and_(Some(2)) == Some(2)
assert Nothing().and_(Some(2)) == Nothing()
assert Some(1).and_(Nothing()) == Nothing()
assert Nothing().and_(Nothing()) == Nothing()
assert Some(1).and_(Nothing()).and_(Some(2)) == Nothing()
Option.or_

Option.or_(alternative: Option[T]) -> Option[T]

如果此 Option 是 Nothing,则返回提供的 alternative Option。否则,如果此 Option 是 Some,则返回其未更改。

示例

assert Some(1).or_(Some(2)) == Some(1)
assert Some(1).or_(Nothing()) == Some(1)
assert Nothing().or_(Some(1)) == Some(1)
assert Nothing().or_(Nothing()) == Nothing()
Option.xor

Option.xor(alternative: Option[T]) -> Option[T]

排他或。如果此 Option 和提供的 alternative 中的一个是 Some,则返回 Some Option。否则,返回 Nothing

示例

assert Some(1).xor(Nothing()) == Some(1)
assert Nothing().xor(Some(1)) == Some(1)
assert Some(1).xor(Some(2)) == Nothing()
assert Nothing().xor(Nothing()) == Nothing()
Option.and_then

Option.and_then(self, fn: t.Callable[[T], Option[U]]) -> Option[U]

如果此 Option 是 Some,则使用包含的值调用提供的、返回 Option 的函数,并返回它返回的任何 Option。如果此 Option 是 Nothing,则返回其未更改。此方法是对 Option.flatmap 的别名。

示例

assert Some(1).and_then(lambda i: Some(i + 1)) == Some(2)
assert Nothing().and_then(lambda i: Some(i + 1)) == Nothing()
Option.flatmap

Option.flatmap(self, fn: t.Callable[[T], Option[U]]) -> Option[U]

如果此 Option 是 Some,则使用包含的值调用提供的、返回 Option 的函数,并返回它返回的任何 Option。如果此 Option 是 Nothing,则返回其未更改。此方法是对 Option.and_then 的别名。

示例

assert Some(1).flatmap(Some) == Some(1)
assert Nothing().flatmap(Some) == Nothing()
Option.or_else

Option.or_else(self, fn: t.Callable[[], Option[T]]) -> Option[T]

如果此 Option 是 Nothing,则调用提供的、返回 Option 的函数,并返回它返回的任何 Option。如果此 Option 是 Some,则返回其未更改。

示例

assert Nothing().or_else(lambda: Some(1)) == Some(1)
assert Some(1).or_else(lambda: Some(2)) == Some(1)
Option.expect

Option.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T

如果此 Option 是 Some,则返回包装的值。否则,如果此 Option 是 Nothing,则引发一个具有提供消息的错误。默认情况下,会引发一个 RuntimeError,但可以通过 exc_cls 关键字参数提供自定义异常类。此方法是对 Option.raise_if_nothing 的别名。

示例

import pytest

with pytest.raises(RuntimeError) as exc:
    Nothing().expect("Nothing here")
    assert str(exc.value) == "Nothing here"

assert Some(1).expect("Nothing here") == 1
Option.raise_if_nothing

Option.raise_if_nothing(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T

如果此 Option 是 Some,则返回包装的值。否则,如果此 Option 是 Nothing,则引发一个具有提供消息的错误。默认情况下,会引发一个 RuntimeError,但可以通过 exc_cls 关键字参数提供自定义异常类。此方法是对 Option.expect 的别名。

示例

import pytest

with pytest.raises(RuntimeError) as exc:
    Nothing().raise_if_nothing("Nothing here")
    assert str(exc.value) == "Nothing here"

assert Some(1).raise_if_nothing("Nothing here") == 1
Option.filter

Option.filter(self, predicate: t.Callable[[T], bool]) -> Option[T]

如果此选项为 Some,则使用提供的谓词函数调用包装的值。如果谓词返回 True,则返回包含此 Option 的包装值的 Some。如果谓词返回 False,则返回 Nothing。如果此 Option 为 Nothing,则返回它不变。

示例

def is_even(val: int) -> bool:
    """Return whether the value is even."""
    return val % 2 == 0

assert Some(2).filter(is_even) == Some(2)
assert Some(1).filter(is_even) == Nothing()
assert Nothing().filter(is_even) == Nothing()
Option.is_nothing

Option.is_nothing(self) -> bool

如果此 Option 为 Nothing,则返回 True。否则,如果此 Option 为 Some,则返回 False。

示例

assert Nothing().is_nothing() is True
assert Some(1).is_nothing() is False
Option.is_some

Option.is_some(self) -> bool

如果此 Option 为 Some,否则,如果此 Option 为 Nothing,则返回 False。

示例

assert Some(1).is_some() is True
assert Nothing().is_some() is False
Option.iter

Option.iter(self) -> t.Iterator[T]

如果此 Option 为 Some,则返回一个长度为一的迭代器,用于包装的值。否则,如果此 Option 为 Nothing,则返回一个 0 长度的迭代器。

示例

assert tuple(Some(1).iter()) == (1,)
assert tuple(Nothing().iter()) == ()
Option.map

Option.map(self, fn: t.Callable[[T], U]) -> Option[U]

如果此 Option 为 Some,则将提供的函数应用于包装的值,并返回包装函数结果的 Some。如果此 Option 为 Nothing,则返回不变的此 Option。

示例

assert Some(1).map(str) == Some("1")
assert Nothing().map(str) == Nothing()
assert Some(1).map(str).map(lambda x: x + "a").map(str.upper) == Some("1A")
Option.map_or

Option.map_or(self, default: U, fn: t.Callable[[T], U]) -> U

如果此 Option 为 Some,则将提供的函数应用于包装的值并返回结果。如果此 Option 为 Nothing,则返回提供的默认值。

示例

assert Some(1).map_or("no value", str) == "1"
assert Nothing().map_or("no value", str) == "no value"
Option.map_or_else

Option.map_or_else(self, default: t.Callable[[], U], fn: t.Callable[[T], U]) -> U

如果此 Option 为 Some,则将提供的函数应用于包装的值并返回结果。如果此 Option 为 Nothing,则调用提供的默认函数(不带参数)并返回结果。

示例

from datetime import datetime, date

assert Some("2005-08-28").map_or_else(
    date.today,
    lambda t: datetime.strptime(t, "%Y-%m-%d").date()
) == datetime(2005, 8, 28).date()

assert Nothing().map_or_else(
    date.today,
    lambda t: datetime.strptime(t, "%Y-%m-%d").date()
) == date.today()
Option.ok_or

Option.ok_or(self, err: E) -> Result[T, E]

如果此 Option 为 Some,则返回包含值的 Ok 结果。否则,返回包含提供的错误的 Err 结果。

示例

assert Some(1).ok_or("no value!") == Ok(1)
assert Nothing().ok_or("no value!") == Err("no value!")
Option.ok_or_else

Option.ok_or_else(self, err_fn: t.Callable[[], E]) -> Result[T, E]

如果此 Option 为 Some,则返回包含值的 Ok 结果。否则,调用提供的 err_fn 并将其返回值包装在 Err 结果中。

示例

from functools import partial

def make_err_msg(msg: str) -> str:
    """Make an error message with some starting text."""
    return f"[MY_APP_ERROR] -- {msg}"

assert Some(1).ok_or_else(partial(make_err_msg, "no value!")) == Ok(1)
assert Nothing().ok_or_else(partial(make_err_msg, "no value!")) == Err(
    "[MY_APP_ERROR] -- no value!"
)
Option.unwrap

Option.unwrap(self) -> T

如果此 Option 为 Some,则返回包装的值。否则,引发一个 RuntimeError

示例

import pytest

assert Some(1).unwrap() == 1

with pytest.raises(RuntimeError):
    Nothing().unwrap()
Option.unwrap_or

Option.unwrap_or(self, default: U) -> t.Union[T, U]

如果此 Option 为 Some,则返回包装的值。否则,返回提供的默认值。

示例

assert Some(1),unwrap_or(-1) == 1
assert Nothing().unwrap_or(-1) == -1
Option.unwrap_or_else

Option.unwrap_or_else(self, fn: t.Callable[[], U]) -> t.Union[T, U]

如果此 Option 为 Some,则返回包装的值。否则,返回提供的函数的结果。

示例

from datetime import date

assert Some(date(2001, 1, 1)).unwrap_or_else(date.today) == date(2001, 1, 1)
assert Nothing().unwrap_or_else(date.today) == date.today()

Option魔术方法

Option.iter

Option.__iter__(self) -> t.Iterator[T]

实现迭代协议,允许迭代 Option.iter 的结果。如果此 Option 为 Ok,则返回一个包含包装值的长度为 1 的迭代器。否则,如果此 Option 为 Nothing,则返回一个 0 长度的迭代器。

示例

# Can be passed to methods that take iterators
assert tuple(Some(1)) == (1,)
assert tuple(Nothing()j) == ()

# Can be used in `for in` constructs, including comprehensions
assert [val for val in Some(1)] == [1]
assert [val for val in Nothing()] == []


# More for-in usage.
for val in Some(1):
    pass
assert val == 1

val = None
for val in Nothing():
    pass
assert val is None
Option.eq

Option.__eq__(self, other: Any) -> bool

启用使用==的相等性检查。

将此 Option 与 other 进行比较。如果 other 是与具有相同包装值的同一类型的 Option,则返回 True。否则,返回 False。

示例

assert (Some(1) == Some(1)) is True
assert (Some(1) == Some(2)) is False
assert (Some(1) == Nothing()) is False
assert (Some(1) == 1) is False
Option.ne

Option.__ne__(self, other: Any) -> bool

启用使用!=的不等性检查。

将 Option 与 other 进行比较。如果 other 是与具有相同包装值的同一类型的 Option,则返回 False。否则,返回 True。

示例

assert (Some(1) != Some(1)) is False
assert (Some(1) != Some(2)) is True
assert (Some(1) != Nothing()) is True
assert (Some(1) != 1) is True
Option.str

Option.__str__(self) -> str

启用使用str()的有用的字符串表示。

示例

assert str(Some(1)) == "Some(1)"
assert str(Nothing()) == "Nothing()"
Option.repr

Option.__repr__(self) -> str

启用使用repr()的有用的字符串表示。

示例

assert repr(Some(1)) == "Some(1)"
assert repr(Nothing()) == "Nothing()"

性能

可以使用 make bench 运行基准测试。在 bench/ 中提供了基准测试工具。

目前,sample.py 基准测试定义了两个数据存储,一个使用经典的 Python 错误处理(或缺乏错误处理),另一个使用此库的包装类型。使用每个数据存储执行一些简单的操作以进行比较。

runner.sh 以两种方式运行基准测试。首先,它使用 hyperfine 将基准测试作为普通的 Python 脚本运行 100 次,并显示运行时间信息。然后,它使用 Python 的内置模块 timeit 来测量代码执行时间,在独立的情况下进行一百万次运行,无需启动解释器来解析和运行脚本。

结果

ResultOption 包装类型对执行时间的开销最小,对于大多数实际工作负载来说不会引起注意。然而,如果在“热点路径”中使用这些类型,则需要小心。

独立运行时,使用 ResultOption 类型的示例代码比内置异常处理慢约六倍

方法 执行次数 平均执行时间 与经典相比
经典 1,000,000 (1E6) 3.79E-6 s (3.79 μs) 1x
包装 1,000,000 (1E6) 2.31E-5 s (23.1 μs) 6.09x

当作为 Python 脚本的一部分运行时,使用这些包装类型与使用内置异常处理和嵌套 if 语句的代码之间没有显著差异。

方法 执行次数 平均执行时间 与经典相比
经典 100 32.2 ms 1x
包装 100 32.5 ms 1.01x

讨论

为了使此库中的包装类型尽可能高效,已采取了一些措施。所有类型都使用 __slots__ 以避免为实例变量分配字典,并且包装变体(例如,OkErr 对于 Result)被实现为 Result 的独立子类,而不是共享的类,以避免执行 if/else 分支或 isinstance() 检查,这在 Python 中尤其缓慢。

话虽如此,使用这些类型 确实是 做了比内置错误处理更多的事情!正在构造实例并访问方法。这两者在 Python 中相对较快,但肯定没有不做任何事情快,因此这个库可能永远不会像原始异常处理那样高效。但话虽如此,这并不是它的目标!目标是尽可能快,最好在常规旧 Python 习惯用法附近,同时提供关于处理错误和缺失数据的显著更佳的易用性和类型安全性。

贡献

欢迎贡献!要开始,您只需在本地上安装 Python 3。

一旦您已分叉并克隆了存储库,您可以运行

  • make test - 使用您本地的解释器运行测试
  • make fmt - 使用 black 格式化代码
  • make lint - 使用各种分析工具检查代码
  • make bench - 运行基准测试

有关其他命令,请参阅 Makefile

CI 系统要求在合并代码之前 make lintmake test 必须成功运行(退出状态为 0)。

result_types 与 Python >= 3.6 兼容。您可以使用 make test-all-versions 运行所有支持的 Python 版本。这需要在您的本地系统上安装 docker。或者,如果您已安装所有必需的 Python 版本,则可以运行 make tox 来在您的本地解释器上运行。

项目详情


下载文件

下载适用于您平台文件的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。

源分发

safetywrap-1.5.0.tar.gz (39.3 kB 查看哈希值)

上传时间:

构建分发

safetywrap-1.5.0-py3-none-any.whl (23.2 kB 查看哈希值)

上传时间: Python 3

支持者