6

我正在为我的库编写一个 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容器中吗?

4

2 回答 2

6

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。

于 2018-09-27T16:16:38.397 回答
4

根据另一个答案pyo3负责围绕我们的函数构建额外的样板,以跟踪 Python 引用计数。特别是,当将对象作为参数传递给函数时,计数器已经递增。然而,该clone_ref方法可用于显式创建对同一对象的新引用,这也将增加其引用计数器。

函数的输出必须仍然是一个实际的 Python 对象,而不是对它的引用(这似乎是合理的,因为 Python 不理解 Rust 引用;pyo3似乎忽略了这些函数中的生命周期参数)。

#[pyfunction]
fn f(py: Python, x: PyObject) -> PyResult<PyObject> {
    Ok(x.clone_ref(py))
}

通过在 Python 领域(AKA 不是一个严肃的测试平台)中使用该函数,它至少似乎按预期工作。

from dummypy import f

def get_object():
    return f("OK")

a = [1, 2, 3]

if True:
    b = f(a)
    assert b is a
    b[0] = 9001

print(a)

x = get_object()
print(x)
于 2018-09-27T16:26:35.677 回答