首页 > 解决方案 > 编写一个等效于 Python 函数的 pyo3 函数,该函数返回其输入对象

问题描述

我正在为我的库编写一个 Rust 后端,我需要在以下函数中实现等价的功能pyo3

def f(x):
    return x

这应该返回与输入相同的对象,并且获取返回值的函数应该持有对输入的新引用。如果我在 C API 中写这个,我会写成:

PyObject * f(PyObject * x) {
    Py_XINCREF(x);
    return x;
}

PyO3PyObject中,我发现导航, PyObjectRef, &PyObject, Py<PyObject>,之间的差异非常令人困惑Py<&PyObject>

这个函数最幼稚的版本是:

extern crate pyo3;

use pyo3::prelude::*;

#[pyfunction]
pub fn f(_py: Python, x: &PyObject) -> PyResult<&PyObject> {
    Ok(x)
}

除其他外,的生命周期x和返回值不一样,加上我看不到pyo3增加引用计数的机会x,事实上编译器似乎同意我的观点:

error[E0106]: missing lifetime specifier
 --> src/lib.rs:4:49
  |
4 | pub fn f(_py: Python, x: &PyObject) -> PyResult<&PyObject> {
  |                                                 ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `_py` or `x`

我可能有一种方法可以使用参数手动增加引用计数_py并使用生命周期注释来使编译器满意,但我的印象是pyo3打算使用对象生命周期来管理引用计数本身。

编写此函数的正确方法是什么?我应该尝试将其包装在Py容器中吗?

标签: pythonrustpyo3

解决方案


APyObject原始指针的简单包装器

pub struct PyObject(*mut ffi::PyObject);

它有多个创建函数,每个函数对应于我们可能从 Python 获得的不同类型的指针。其中一些,例如from_borrowed_ptr,调用Py_INCREF传入的指针。

因此,我们似乎可以接受 a PyObject,只要它是以“正确”的方式创建的。

如果我们展开这段代码:

#[pyfunction]
pub fn example(_py: Python, x: PyObject) -> PyObject {
    x
}

我们可以看到调用我们函数的这段代码:

let mut _iter = _output.iter();
::pyo3::ObjectProtocol::extract(_iter.next().unwrap().unwrap()).and_then(
    |arg1| {
        ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(example(
            _py, arg1,
        ))
    },
)

我们的论点是由对 的调用创建的ObjectProtocol::extract,而后者又调用FromPyObject::extract. 这是通过调用来实现的PyObjectfrom_borrowed_ptr

因此,使用 barePyObject作为参数类型将正确地增加引用计数。

同样,当PyObject在 Rust 中删除 a 时,它会自动减少引用计数。当它返回给 Python 时,所有权被转移,由 Python 代码来适当地更新引用计数。


从主分支对提交 ed273982进行的所有调查,对应于 v0.5.0-alpha.1。


推荐阅读