3个不稳定版本

0.2.0 2024年8月13日
0.1.0 2024年6月28日
0.1.0-beta.12024年6月25日

#862 in 数据库接口

Download history 327/week @ 2024-06-24 29/week @ 2024-07-01 151/week @ 2024-08-12

每月151次下载

MIT/Apache

155KB
3.5K SLoC

pyo3-arrow

crates.io version docs.rs docs

轻量级的Apache Arrow集成,用于pyo3。旨在使Rust库更容易添加互操作、零拷贝的Python绑定。

具体来说,pyo3-arrow使用arrowcrate实现了Python对象和Rust表示之间的零拷贝FFI转换。这依赖于Arrow PyCapsule接口,以实现Python Arrow生态系统中的无缝互操作性。

使用方法

我们只需用几行代码就可以包装一个在Python中使用的函数。

当您将定义在pyo3_arrow中的结构体用作函数的参数时,它将通过零拷贝FFI自动将用户输入转换为Rust arrow对象。然后,一旦完成,调用to_arro3to_pyarrow将数据导回到Python。

use pyo3::prelude::*;
use pyo3_arrow::error::PyArrowResult;
use pyo3_arrow::PyArray;

/// Take elements by index from an Array, creating a new Array from those
/// indexes.
#[pyfunction]
pub fn take(py: Python, values: PyArray, indices: PyArray) -> PyArrowResult<PyObject> {
    // We can call py.allow_threads to ensure the GIL is released during our
    // operations
    // This example just wraps `arrow_select::take::take`
    let output_array =
        py.allow_threads(|| arrow_select::take::take(values.as_ref(), indices.as_ref(), None))?;

    // Construct a PyArray and export it to the arro3 Python Arrow
    // implementation
    Ok(PyArray::new(output_array, values.field().clone()).to_arro3(py)?)
}

然后在Python端,我们可以调用这个函数(通过arro3.compute.take导出)

import pyarrow as pa
from arro3.compute import take

arr = pa.array([2, 3, 0, 1])
output = take(arr, arr)
output
# <arro3.core._rust.Array at 0x10787b510>
pa.array(output)
# <pyarrow.lib.Int64Array object at 0x10aa11000>
# [
#   0,
#   1,
#   2,
#   3
# ]

在这个例子中,我们使用pyarrow创建原始数组并查看结果,但不需要使用pyarrow。它确实展示了Arrow PyCapsule接口如何使这些Arrow对象在Python Arrow实现之间共享变得无缝。

使用Arrow数据作为输入

只需在您的函数签名中包含pyo3-arrow的一个结构体,用户输入将自动转换

这使用了Arrow PyCapsule接口。但请注意,它只定义了三个方法,而pyo3-arrow包含更多的结构体。几个结构体是重载的,并使用相同的底层传输机制。

例如,PySchemaPyField都使用__arrow_c_schema__机制,但行为不同。前者期望传输的字段是结构体类型,其子节点会被解包为模式中的字段,而后者没有约束,以原样传递字段。PySchema如果传入的字段不是结构体类型,则会出错。

结构体名称 解包结构体字段
PySchema
PyField

PyArrayPyRecordBatch 都使用了 __arrow_c_array__ 机制。

结构体名称 StructArray 解包为 RecordBatch
PyRecordBatch
PyArray

PyTablePyChunkedArrayPyRecordBatchReader 都使用了 __arrow_c_stream__ 机制。

结构体名称 StructArray 解包为 RecordBatch 在内存中实例化。
PyTable
PyRecordBatchReader
PyChunkedArray
PyArrayReader

将 Arrow 数据返回到 Python。

使用您自己的类

如果您正在将您自己的 Arrow 兼容类导出到 Python,您可以直接在您自己的类上实现相关的 Arrow PyCapsule 接口方法。

要导出流数据,请添加一个具有以下签名的类方法

use pyo3_arrow::ffi::to_stream_pycapsule;

fn __arrow_c_stream__<'py>(
    &'py self,
    py: Python<'py>,
    requested_schema: Option<Bound<PyCapsule>>,
) -> PyResult<Bound<'py, PyCapsule>> {

    // Construct a PyTable from your data
    let table: PyTable = todo!();
    table.__arrow_c_stream__(py, requested_schema)
}

导出模式或数组数据类似于,只是使用 __arrow_c_schema____arrow_c_array__ 方法。

如果您不想导出您自己的类,请参考以下解决方案。

使用 arro3.core

arro3.core 是一个非常轻量级的 Python Arrow 实现,设计用于轻量级(<1MB)和相对稳定。相比之下,pyarrow 大约是 ~100MB。

您必须依赖于 arro3-core Python 包;然后您可以使用每个导出的 Arrow 对象的 to_arro3 方法将数据传递到 arro3.core 类。

Rust 结构体 arro3 类
PyField arro3.core.字段
PySchema arro3.core.模式
PyArray arro3.core.数组
PyRecordBatch arro3.core.RecordBatch
PyChunkedArray arro3.core.ChunkedArray
PyTable arro3.core.Table
PyRecordBatchReader arro3.core.RecordBatchReader

使用 pyarrow

pyarrow,标准的 Python Arrow 实现,是一个非常庞大的依赖项。它本身大约有 100MB 的大小,加上对 numpy 的强依赖性,大约还有 35MB。然而,numpy 很可能在用户环境中已经存在,pyarrow 也相当常见,因此要求 pyarrow 依赖项可能不是问题。

在这种情况下,您必须依赖于 pyarrow,并且您可以使用 Python 结构体的 to_pyarrow 方法将数据返回到 Python。这需要 pyarrow>=14(要返回 PyRecordBatchReader,则需要 pyarrow>=15)。

Rust 结构体 pyarrow 类
PyField pyarrow.字段
PySchema pyarrow.模式
PyArray pyarrow.数组
PyRecordBatch pyarrow.RecordBatch
PyChunkedArray pyarrow.ChunkedArray
PyTable pyarrow.Table
PyRecordBatchReader pyarrow.RecordBatchReader

使用 nanoarrow

nanoarrow 是一个用于处理 Arrow 数据的替代 Python 库。它的目标与 arro3 类似,但它是用 C 而不是 Rust 编写的。此外,它比 pyarrowarro3 具有更小的类型系统,逻辑数组和记录批次都由 nanoarrow.Array 类表示。

在这种情况下,您必须依赖于 nanoarrow,并且您可以使用 Python 结构体的 to_nanoarrow 方法将数据返回到 Python。

Rust 结构体 nanoarrow 类
PyField nanoarrow.模式
PySchema nanoarrow.模式
PyArray nanoarrow.数组
PyRecordBatch nanoarrow.数组
PyChunkedArray nanoarrow.ArrayStream
PyTable nanoarrow.ArrayStream
PyRecordBatchReader nanoarrow.ArrayStream

为什么不使用 arrow-rs 的 Python 集成?

arrow-rs 有 一些现有的 Python 集成,但有几个原因促使我创建了 pyo3-arrow

  • arrow-rs 只支持将数据返回给 pyarrow。Pyarrow 是一个非常大的依赖项(其未打包的 Linux 轮文件大小为 130MB,不包括对 Numpy 的依赖),有些项目可能不希望使用它。现在有了 Arrow PyCapsule 接口,可以采用模块化方法,其中一个非常小的库包含核心 Arrow 对象,并与其他库无缝协作。
  • arrow-rs 的 Python FFI 集成不支持 Arrow 扩展类型,因为它在构建 Arc<dyn Array> 时省略了字段元数据。pyo3-arrow 通过在 PyArray 结构体中存储一个 ArrayRef(《Arc<dyn Array>)和一个 FieldRef(《Arc<Field>)来解决这个问题。
  • arrow-rs 无法处理不是记录批次的裸数组 Arrow 流,因此它无法与 pyarrow.ChunkedArraypolars.Series 进行互操作。
  • 在我看来,arrow-rs 与 pyo3 和 pyarrow 的联系过于紧密。pyo3 的发布版本与 arrow-rs 的发布周期不匹配,这意味着在使用 arrow-rs 时可能需要等待一段时间才能使用最新的 pyo3 版本,尤其是在 arrow-rs 等待较长时间发布破坏性更改的情况下。

依赖项

~18–26MB
~381K SLoC