Skip to main content

Python Rusty Result Library: 在Python中使用Rust风格的Result类型

Project description

PyRsult

Python Rusty Result Library:在 Python 中使用 Rust 风格的 ResultOption 类型。

版本:0.4.0 | GitHub


安装

pip install git+https://github.com/fexcode/pyrsult.git

快速上手

from pyrsult import Result, Option

# Result:要么成功,要么失败
def div(a: int, b: int) -> Result[float, str]:
    if b == 0:
        return Result.Failure("divide by zero")
    return Result.Success(a / b)

r = div(10, 2)
print(r.unwrap())              # 5.0
print(r.map(lambda x: x * 2))  # Success(10.0)

# Option:要么有值,要么空
def find(lst, target):
    for v in lst:
        if v == target:
            return Option.Some(v)
    return Option.Nothing()

find([1, 2, 3], 2).map(lambda x: x * 10).unwrap_or(0)  # 20

Result 详解

Result[T, E] 是一个泛型联合类型,有两个分支:

  • Success[T] — 操作成功,携带值 T
  • Failure[E] — 操作失败,携带错误 E

构造

Result.Success(value)  # → Success(value)
Result.Failure(error)  # → Failure(error)

SuccessFailure 也可直接用作构造器(也支持 match 模式匹配)。

判别

is_ok() -> bool

判断当前值是否是 Success

Result.Success(42).is_ok()  # True
Result.Failure("err").is_ok()  # False

is_err() -> bool

判断当前值是否是 Failure

Result.Failure("err").is_err()  # True
Result.Success(42).is_err()  # False

取值

unwrap() -> T

取出 Success 内部的值。如果当前是 Failure,则抛出 ValueError

Result.Success(42).unwrap()           # 42
Result.Failure("bad").unwrap()        # ValueError: Unwrap failed | bad

unwrap_err() -> E

取出 Failure 内部的错误值。如果当前是 Success,则抛出 ValueError

Result.Failure("bad").unwrap_err()    # 'bad'
Result.Success(42).unwrap_err()       # ValueError: Called unwrap_err on an Ok value

unwrap_or(default: T) -> T

取出 Success 内部的值;如果是 Failure,则返回 default

Result.Failure("bad").unwrap_or(0)    # 0
Result.Success(42).unwrap_or(0)       # 42

unwrap_or_else(func: Callable[[E], T]) -> T

取出 Success 内部的值;如果是 Failure,则将错误传入 func 并返回其结果(懒求值)。

Result.Failure("bad").unwrap_or_else(lambda e: len(e))  # 3
Result.Success(42).unwrap_or_else(lambda e: 0)           # 42

expect(msg: str) -> T

取出 Success 内部的值;如果是 Failure,则抛出 ValueError,异常消息为 msg

Result.Success(42).expect("custom")     # 42
Result.Failure("bad").expect("custom")  # ValueError: custom

变换

map(func: Callable[[T], U]) -> Result[U, E]

如果当前是 Success,对其内部值应用 func 并返回新的 SuccessFailure 保持不变。

Result.Success(10).map(lambda x: x * 2)  # Success(20)
Result.Failure("bad").map(lambda x: x)   # Failure('bad')

map_err(func: Callable[[E], F]) -> Result[T, F]

如果当前是 Failure,对其内部错误应用 func 并返回新的 FailureSuccess 保持不变。

Result.Failure("bad").map_err(str.upper)  # Failure('BAD')
Result.Success(42).map_err(str.upper)     # Success(42)

链式

and_then(func: Callable[[T], Result[U, E]]) -> Result[U, E]

如果当前是 Success,则将内部值传入 funcfunc 必须返回 Result),实现扁平化链式调用。类似 Rust 的 and_then / flat_mapFailure 保持不变。

def safe_div(x: float) -> Result[float, str]:
    return Result.Failure("zero") if x == 0 else Result.Success(10 / x)

Result.Success(2.0).and_then(safe_div)   # Success(5.0)
Result.Success(0.0).and_then(safe_div)   # Failure('zero')
Result.Failure("err").and_then(safe_div) # Failure('err')

or_else(func: Callable[[E], Result[T, F]]) -> Result[T, F]

如果当前是 Failure,则将错误传入 func 进行补救;Success 保持不变。

def补救(e: str) -> Result[int, str]:
    return Result.Success(42)

Result.Failure("bad").or_else(补救)      # Success(42)
Result.Success(10).or_else(补救)         # Success(10)

模式匹配

Python 3.10+ 支持直接对 Result 进行 match

match div(10, 0):
    case Success(v):
        print("ok", v)
    case Failure(e):
        print("err", e)   # err divide by zero

Option 详解

Option[T] 用于表示一个值可能存在也可能不存在,有两个分支:

  • Some[T] — 值存在
  • Nothing — 值为空

构造

Option.Some(value)   # → Some(value)
Option.Nothing()     # → Nothing
Option.From(value)   # value 不为 None → Some(value),否则 → Nothing()

SomeNothing 也可直接使用。

判别

is_some: bool(属性)

判断是否为 Some

Option.Some(42).is_some       # True
Option.Nothing().is_some      # False

is_nothing: bool(属性)

判断是否为 Nothing

Option.Nothing().is_nothing   # True
Option.Some(42).is_nothing    # False

取值

unwrap() -> T

取出 Some 内部的值。如果为 Nothing 则抛出 ValueError

Option.Some(42).unwrap()      # 42
Option.Nothing().unwrap()     # ValueError: 这个值是 Nothing

unwrap_or(default: T) -> T

取出 Some 内部的值;Nothing 时返回 default

Option.Nothing().unwrap_or(0)  # 0
Option.Some(42).unwrap_or(0)   # 42

unwrap_or_else(func: Callable[[], T]) -> T

取出 Some 内部的值;Nothing 时调用 func 并返回其结果(懒求值)。

Option.Nothing().unwrap_or_else(lambda: 99)  # 99
Option.Some(42).unwrap_or_else(lambda: 99)   # 42

变换

map(func: Callable[[T], U]) -> Option[U]

如果为 Some,对内部值应用 func 并返回新的 SomeNothing 保持不变。

Option.Some(7).map(lambda x: x * 2)   # Some(14)
Option.Nothing().map(lambda x: x)     # Nothing

map_or(default: U, func: Callable[[T], U]) -> U

map + unwrap_or 的组合:Some 时应用 funcNothing 时返回 default

Option.Some(7).map_or(0, lambda x: x * 2)   # 14
Option.Nothing().map_or(0, lambda x: x * 2)  # 0

map_or_else(default: Callable[[], U], func: Callable[[T], U]) -> U

map + unwrap_or_else 的组合,两个参数均为懒求值。

Option.Some(7).map_or_else(lambda: 0, lambda x: x * 2)   # 14
Option.Nothing().map_or_else(lambda: 0, lambda x: x * 2)  # 0

链式

and_then(func: Callable[[T], Option[U]]) -> Option[U]

如果为 Some,将内部值传入 funcfunc 必须返回 Option),实现扁平化链式调用。类似 Rust 的 and_then

def parse_int(s: str) -> Option[int]:
    try:
        return Option.Some(int(s))
    except ValueError:
        return Option.Nothing()

Option.Some("42").and_then(parse_int)  # Some(42)
Option.Some("abc").and_then(parse_int) # Nothing
Option.Nothing().and_then(parse_int)   # Nothing

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

如果为 Some 且满足 predicate,则保留;否则变为 Nothing

Option.Some(7).filter(lambda x: x > 5)   # Some(7)
Option.Some(3).filter(lambda x: x > 5)   # Nothing
Option.Nothing().filter(lambda x: x > 5) # Nothing

备选

or_(optb: Option[T]) -> Option[T]

当前为 Some 时返回自身,否则返回 optb

Option.Some(1).or_(Option.Some(2))   # Some(1)
Option.Nothing().or_(Option.Some(2)) # Some(2)

or_else(func: Callable[[], Option[T]]) -> Option[T]

当前为 Some 时返回自身,否则调用 func 并返回其结果(懒求值)。

Option.Nothing().or_else(lambda: Option.Some(99))  # Some(99)
Option.Some(1).or_else(lambda: Option.Some(99))    # Some(1)

类型转换

ok_or(err: E) -> Union[T, E]

Option 转为"值或错误"风格:Some(v)vNothingerr

Option.Some(5).ok_or("empty")   # 5
Option.Nothing().ok_or("empty") # 'empty'

迭代

iter() -> Iterator[T]

返回包含 0 或 1 个元素的迭代器,方便 for 循环。

for v in Option.Some(42).iter():
    print(v)  # 42

list(Option.Some(3).iter())   # [3]
list(Option.Nothing().iter()) # []

运算符

__bool__

Option 可直接用于布尔上下文。

if Option.Some(42):
    print("有值")  # 输出:有值

bool(Option.Nothing())  # False
bool(Option.Some(42))   # True

| 运算符

a | b 等价于 a.or_(b),取第一个非空值。

Option.Nothing() | Option.Some(3)   # Some(3)
Option.Some(1) | Option.Some(2)     # Some(1)

快捷属性

Sm -> Some[T]

当确定当前值一定是 Some 时,直接获取 Some 包装(而非内部值)。若实际为 Nothing 则抛出 ValueError

Option.Some(5).Sm  # Some(5)

NN -> T

iam_sure_this_value_is_not_nothing 的别名。当确定当前值一定不是 Nothing 时,直接取出内部值。若实际为 Nothing 则抛出 ValueError。用法同 unwrap() 但不抛 ValueError 的包装异常。

Option.Some(5).NN  # 5

实战:读取配置

import os
from pyrsult import Result, Option

def env_int(key: str) -> Result[int, str]:
    val = os.getenv(key)
    return (
        Option.From(val)
        .map(int)
        .ok_or(f"{key} not a valid int")
    )

port = env_int("PORT").unwrap_or(8080)

欢迎 PR & Issue!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrsult-0.5.0.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyrsult-0.5.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file pyrsult-0.5.0.tar.gz.

File metadata

  • Download URL: pyrsult-0.5.0.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyrsult-0.5.0.tar.gz
Algorithm Hash digest
SHA256 8718e6e5967f6a08dcff467b110804b8f064128475a095a81dcd24342a1cb4f7
MD5 37904f727d302a59a552a8dc4b7df59d
BLAKE2b-256 eae9bf5bbd8f2639dae2addc4672ff21c6022e06a63ac28a6a7366c1e9c84ce1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyrsult-0.5.0.tar.gz:

Publisher: python-publish.yml on fexcode/pyrsult

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyrsult-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: pyrsult-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 7.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyrsult-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 015094745ed41e44b045664c2ea5f8d7ae35d88be646bf48a5c578024f2d167e
MD5 6ba138a94e34ad8beddfb32054256f61
BLAKE2b-256 db0b6a6133b273e0cb4f790f9b58c142cef721948d52d15aa4d6c21df2bbc9ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyrsult-0.5.0-py3-none-any.whl:

Publisher: python-publish.yml on fexcode/pyrsult

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page