ITADN
OutSquareCapital/pyochain · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

pyochain

pyochain 是一个 Python 库,提供具有流畅 API 的各种类,用于处理迭代集合处理可选值管理错误等!

注意: 本文档与 dev 分支保持同步,可能包含尚未在最新稳定版本中发布的功能。

目录


主要特性

  • 使用 Option[T] 处理可选值,而非 T | None
  • 使用 Result[T, E] 处理成功和错误路径,而非 try.. except 块。
  • 针对 Python 内置类型(zipmap、...)和 itertoolsIterator 类型,以及 Rust Iterator 中的许多方法,还有 toolzmore-itertools 等库。
  • 覆盖 Python 内置类型(listdequeset、...)的 Collection 类型,额外的无拷贝切片视图,以及通过两个专用类型(HeapMaxHeapMin)重新实现的 heapq 模块。
  • 模仿 collections.abcABC 层级结构,用于鸭子类型、共享方法,并允许实现自定义子类。
  • 可添加到任何类的 Mixin,通过 pipetap 提供流畅 API,或在真值评估时进行 Option/Result 转换。

核心设计原则

  • 使用 Rust 编译,借助 Pyo3 实现最大性能。
  • 流畅 API 设计用于链式调用方法,使代码阅读如同阅读书籍 => 从上到下,从左到右。
  • 一等静态类型支持:针对 OptionResult 类型的泛型、重载和模式匹配。

这就是它被称为 pyochain 的原因:它允许你构建针对数据的操作,代码通过 Pyo3 在 Rust 中编译。

安装

Pyochain 支持 Python 3.13 及以上版本,并为 Linux、Windows 和 MacOS 提供编译好的 wheel 包。

uv add pyochain # or pip install pyochain

链接

🐍Pypi 软件包

📚 完整 API 参考

📄 README.md

为什么使用 pyochain?

🔥 极速性能

由于采用静态编译,pyochain 在设计上比其他类似的 Python 库快一个数量级。

例如,像 x = Vec([1, 2, 3]) 这样简单的单对象创建,比 Vec 用纯 Python 实现时快 30%。

对于 Iterator 方法和类,这种加速效果更为显著,通常比 more-itertools 的等价实现快 2 到 10 倍

即使源代码大部分仍是 Python,也付出了巨大努力来优化性能,这或许就是它在该类别中已被评为最快库的原因,参见此对比(此时,只有 ResultOption 是编译过的,它们与此基准测试无关)。

🌍 丰富的生态系统

Pyochain 旨在作为许多内置类型和函数的直接替代品,提供广泛的功能,旨在与彼此以及更广泛的 Python 生态系统无缝交互。

未来计划了许多额外的功能,包括 Array、排序集合、唯一可变序列等。

🛡️ 100% 类型安全

IDE 自动补全是一个主要关注点,pyochain 为其所有构造提供了详尽的重载和泛型支持。

在某些情况下,它甚至比 typeshed 更完整,例如 map_star 在参数和返回类型方面完全类型化,而 itertools.starmap 则不是。

这是开发库时最难测试的部分,因此如果您遇到任何类型问题,请报告!

📚 准确、经过测试的文档

每个方法和类型都有详尽的文档,并包含经过正确性测试的可运行示例。

甚至这个 README 也经过了测试!


入门指南

Mixin

PipeTapCheckable 以及其他 mixin 可以添加到任何类中,以添加新方法,无论底层实现如何。

除了 __bool__ 之外,它们不依赖于内部状态,因此可以普遍应用于任何子类,包括您自己的子类。

pandaspolars 或 Rust crate tap 的用户会对 pipe()tap 感到如鱼得水,而 Rust 开发者则会欣赏 Checkable 类型,它提供了诸如 thenthen_someok_or_else 之类的方法,通过评估实例的真值性来返回相应的 OptionResult 类型,就像 Rust 中的 bool 方法一样。

所有 pyochain 类型都继承自它们,这使得针对集合空值或错误处理的控制流成为管道的一部分。

迭代器

Pyochain 具有各种 Iterator 类型,拥有流畅的 API,其功能来自:

  • Python builtins(完全覆盖)=> zipmap、...
  • itertools 模块(完全覆盖)=> chaincombinations、...
  • Rust std::iter::Iterator => try_collectpartition、...
  • more-itertools=> all_uniquearg_maxtail、...
  • toolz=> map_juxtcountfirst、...

它们可以用于以可读的方式构建复杂的转换、过滤和聚合管道(无需嵌套循环或推导式),而无需创建中间集合(惰性执行),从而改善性能和内存使用。

许多方法与其 Rust 对应方法行为一致:filter_map 过滤由闭包返回的 Optionfind 返回匹配谓词的第一个元素并包裹在 Option 中,等等...

所有与迭代器相关的源代码均以 Rust 实现,并调用内部实现或 CPython 内置函数。

这保证了在性能上没有任何权衡,从迭代器实例化本身到实际的迭代过程。

以下是如何使用 Iter 的示例,以及它与使用 itertools 的纯 Python 实现的对比:

from pyochain import Iter, Seq
import itertools

wanted = ((0, "1"), (1, "9"), (2, "25"), (3, "49"), (4, "81"))

pyochain_res = (
    Iter
    .from_count(1)
    .filter(lambda x: x % 2 != 0)
    .map(lambda x: x**2)
    .take(5)
    .enumerate()
    .map_star(lambda idx, value: (idx, str(value)))
    .collect(tuple)
)
py_res = tuple(
    itertools.islice(
        itertools.starmap(
            lambda idx, val: (idx, str(val)),
            enumerate(
                map(lambda x: x**2, filter(lambda x: x % 2 != 0, itertools.count(1)))
            ),
        ),
        5,
    )
)
assert pyochain_res == py_res == wanted

集合

每种 Python 内置集合类型(list、tuple、range、dict、set 等)都有对应的 pyochain 类型,此外还有诸如 SliceView(切片的无拷贝视图)或 StableSet(保持插入顺序的可变集合)等集合,未来还将规划更多类型。

许多方法被设计用于与库的其他部分互操作:Dict::get_itemSeq::get 返回一个 OptionVec::drain 返回一个 PyoIterator,依此类推。

from pyochain import Dict, Iter, Some
from pyochain.collections import StableSet

names = ["Charlie", "Alice", "Bob", "Alice"]


# Create a Dict from an iterator of key-value pairs
data = Iter.from_count().zip(names).collect(Dict)
assert data == Dict({0: "Charlie", 1: "Alice", 2: "Bob", 3: "Alice"})


# try_insert returns a Result, which is Err if the key already exists
err = data.try_insert(1, "David")
assert err.is_err()

# sort return a Vec
vals = data.values().iter().map(str.upper).sort()

assert vals.first() == "ALICE"
assert vals.len() == 4
# Modify the Vec in place with retain according to the predicate
vals.retain(lambda x: x.endswith("E"))
assert vals.len() == 3

# Create a set of unique names, preserving insertion order, with StableSet
unique_names = vals.pipe(StableSet)
assert unique_names == {"ALICE", "CHARLIE"}
assert unique_names.iter().next() == Some("ALICE")

结果与选项

使用专用类型以显式方式处理 None 和异常,而不是依赖隐式真值检查或 try/except 块。

成功和失败可以分别由 Ok[T]Err[E] 类型表示,而可选值可以由 Some[T]Null 表示。

这使每条路径都变得显式、更少出错且更易读,通过用类似 x.map().and_then().unwrap_or() 的单一管道替换嵌套的 try/except 块和 if x is not None 检查。

对于熟悉此模式的用户,其 Rust 对应物中存在的几乎所有方法均可用,此外还提供了额外的便捷方法以兼容广泛的 python 生态系统,例如 unwrap_or_none()(我知道这是异端)。

from pyochain import Option, NONE, Some, Seq, Vec, Set, Ok, Err, Result
from pyochain.abc import PyoIterable


def divide(a: int, b: int) -> Option[float]:
    return NONE if b == 0 else Some(a / b)


assert divide(10, 2) == Some(5.0)
# Provide a default value
assert divide(10, 0).unwrap_or(-1.0) == -1.0
# Convert between Collections -> Option -> Result
tup = (1, 2, 3)
seq = Seq(tup)
assert seq.then_some() == Some(Seq(tup))
assert seq.then_some().ok_or("No values").unwrap() == Seq(tup)


# Accept any Pyochain Iterable
def _process(data: PyoIterable[int]) -> str:
    return data.iter().map(str).join(", ")


# Process only if non-empty, convert Option to Result
assert seq.then(_process).ok_or("No values").unwrap() == "1, 2, 3"
assert (
    Vec(()).then(_process).ok_or("No values").expect_err("expected error")
    == "No values"
)
# Create empty Set, convert to Result, then back to Option
assert Set(()).then(_process).ok_or("No values").ok() is NONE

使用模式匹配进行类型安全的穷举处理

类型检查器将确保在匹配 OptionResult 类型时处理所有情况。

from pyochain import Result, Ok, Err


def try_parse_int(s: str) -> Result[int, ValueError]:
    try:
        return Ok(int(s))
    except ValueError as e:
        return Err(e)


def handle_result(res: Result[int, ValueError]) -> str:
    match res:
        case Ok(value):
            return f"Parsed value: {value}"
        case Err(_):
            return f"Error parsing int!"


assert try_parse_int("123").pipe(handle_result) == "Parsed value: 123"
assert try_parse_int("abc").pipe(handle_result) == "Error parsing int!"

ABC's

提供了一个模仿 collections.abc 模块的类层次结构,每个类仅需与其标准库对应项相同的 dunders(例如,PyoIterable 对应 __iter__),同时提供了许多受 Rust 启发的附加方法,如 PyoMutableSequence::retainPyoMutableMapping::try_insert,以及当然还有来自 PyoIterator 的众多功能。

所有具体类型 IteratorCollection 都实现了这些接口,因此您可以使用它们进行类型检查、实现自己的子类,并无缝替换 Python 生态系统中的类型,且无需权衡取舍。

from pyochain import Some
from pyochain.abc import PyoSequence, PyoIterable
from dataclasses import dataclass
from collections.abc import Sequence


@dataclass(slots=True)
class MySequence(PyoSequence[int]):
    data: list[int]

    def __len__(self) -> int:
        return len(self.data)

    def __getitem__(self, index: int) -> int:
        return self.data[index]


x = MySequence([1, 2, 3])
# Call any method from PyoSequence, like first(), last(), get(), etc...
assert x.get(2) == Some(3)
# Convert to an Iterator and immediately benefit from all the Iterator methods
assert x.iter().map(lambda x: x**2).collect(tuple) == (1, 4, 9)
# Convert to an Option just like pyochain core API
assert x.then_some() == Some(x)
# Works with runtime instance and subclass type checking
assert isinstance(x, PyoSequence)
assert isinstance(x, PyoIterable)
assert isinstance(x, Sequence)
assert issubclass(MySequence, PyoSequence)
assert issubclass(MySequence, Sequence)

稳定性声明 ⚠️

pyochain 目前处于早期开发阶段(< 1.0),在达到稳定的 1.0 版本之前,API 可能会经历多次重大变更。

贡献

我们正在积极寻找贡献者来帮助我们改进 pyochain!如果您有兴趣参与贡献,请阅读我们的贡献指南以获取有关如何开始的更多信息。

致谢

大多数自定义计算算法的灵感来源于 itertoolscytoolzmore-itertools 中的实现。

使用 Pyo3 在 Rust 中编译该库,并提供与 Python 的无缝集成。

使用 Polars 让我意识到从上到下阅读代码是编写 Python 的更好方式,同时也让我接触到了 Rust。

Star History

Star History Chart