29 个版本

0.22.2 2024年7月17日
0.22.0 2024年6月24日
0.21.0 2024年3月25日
0.20.1 2023年12月30日
0.16.2 2022年3月15日

176FFI

Download history 499233/week @ 2024-04-29 529106/week @ 2024-05-06 523904/week @ 2024-05-13 570433/week @ 2024-05-20 427898/week @ 2024-05-27 458497/week @ 2024-06-03 622752/week @ 2024-06-10 471708/week @ 2024-06-17 535665/week @ 2024-06-24 427136/week @ 2024-07-01 547349/week @ 2024-07-08 582237/week @ 2024-07-15 591963/week @ 2024-07-22 694111/week @ 2024-07-29 823037/week @ 2024-08-05 804787/week @ 2024-08-12

每月下载量 2,933,081
用于 864 个 crate(直接使用 3 个)

MIT/Apache

365KB
8K SLoC

pyo3-ffi

此crate提供了Python 3的Rust FFI声明。它通过使用cfg标志支持ABI的稳定和不稳定组件。支持Python版本3.7及以上。仅适用于高级用户 - 定期使用PyO3的用户根本不需要与这个crate进行交互。

此crate的内容在此处没有文档说明,因为这基本上涉及从CPython复制文档。请参阅Python/C API参考手册以获取最新文档。

最低支持的Rust和Python版本

PyO3支持以下软件版本

  • Python 3.7及以上(CPython和PyPy)
  • Rust 1.63及以上

示例:构建Python本地模块

PyO3可用于生成原生Python模块。第一次尝试此功能的最简单方法是使用maturinmaturin是一个用于使用最小配置构建和发布基于Rust的Python包的工具。以下步骤为示例Python模块设置一些文件,安装maturin,然后展示如何构建和导入Python模块。

首先,创建一个新文件夹(让我们称它为string_sum),其中包含以下两个文件

Cargo.toml

[lib]
name = "string_sum"
# "cdylib" is necessary to produce a shared library for Python to import from.
#
# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
# to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
# crate-type = ["cdylib", "rlib"]
crate-type = ["cdylib"]

[dependencies.pyo3-ffi]
version = "*"
features = ["extension-module"]

src/lib.rs

use std::os::raw::c_char;
use std::ptr;

use pyo3_ffi::*;

static mut MODULE_DEF: PyModuleDef = PyModuleDef {
    m_base: PyModuleDef_HEAD_INIT,
    m_name: c_str!("string_sum").as_ptr(),
    m_doc: c_str!("A Python module written in Rust.").as_ptr(),
    m_size: 0,
    m_methods: unsafe { METHODS.as_mut_ptr().cast() },
    m_slots: std::ptr::null_mut(),
    m_traverse: None,
    m_clear: None,
    m_free: None,
};

static mut METHODS: [PyMethodDef; 2] = [
    PyMethodDef {
        ml_name: c_str!("sum_as_string").as_ptr(),
        ml_meth: PyMethodDefPointer {
            _PyCFunctionFast: sum_as_string,
        },
        ml_flags: METH_FASTCALL,
        ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
    },
    // A zeroed PyMethodDef to mark the end of the array.
    PyMethodDef::zeroed()
];

// The module initialization function, which must be named `PyInit_<your_module>`.
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
    PyModule_Create(ptr::addr_of_mut!(MODULE_DEF))
}

pub unsafe extern "C" fn sum_as_string(
    _self: *mut PyObject,
    args: *mut *mut PyObject,
    nargs: Py_ssize_t,
) -> *mut PyObject {
    if nargs != 2 {
        PyErr_SetString(
            PyExc_TypeError,
            c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
        );
        return std::ptr::null_mut();
    }

    let arg1 = *args;
    if PyLong_Check(arg1) == 0 {
        PyErr_SetString(
            PyExc_TypeError,
            c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
        );
        return std::ptr::null_mut();
    }

    let arg1 = PyLong_AsLong(arg1);
    if !PyErr_Occurred().is_null() {
        return ptr::null_mut();
    }

    let arg2 = *args.add(1);
    if PyLong_Check(arg2) == 0 {
        PyErr_SetString(
            PyExc_TypeError,
            c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
        );
        return std::ptr::null_mut();
    }

    let arg2 = PyLong_AsLong(arg2);
    if !PyErr_Occurred().is_null() {
        return ptr::null_mut();
    }

    match arg1.checked_add(arg2) {
        Some(sum) => {
            let string = sum.to_string();
            PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as isize)
        }
        None => {
            PyErr_SetString(
                PyExc_OverflowError,
                c_str!("arguments too large to add").as_ptr(),
            );
            std::ptr::null_mut()
        }
    }
}

在放置好这两个文件后,现在需要安装 maturin。这可以通过使用 Python 的包管理器 pip 来完成。首先,启动一个新的 Python virtualenv,并将 maturin 安装到其中

$ cd string_sum
$ python -m venv .env
$ source .env/bin/activate
$ pip install maturin

现在构建并执行模块

$ maturin develop
# lots of progress output as maturin runs the compilation...
$ python
>>> import string_sum
>>> string_sum.sum_as_string(5, 20)
'25'

除了使用 maturin 之外,还可以使用 setuptools-rust手动 构建。两者都比 maturin 提供更多的灵活性,但需要进一步的配置。

虽然大多数项目使用 PyO3 提供的安全包装,但你也可以查看 orjson 库作为如何直接使用 pyo3-ffi 的示例。对于熟悉 C 和 Rust 的人来说,CPython 文档中的 教程 可以很容易地转换为 Rust。

依赖关系