编写与返回其输入对象的Python函数等效的pyo3函数

时间:2018-09-27 13:41:56

标签: python rust pyo3

我正在为自己的库编写Rust后端,我需要在pyo3中实现以下功能的等效项:

def f(x):
    return x

这应该返回相同的对象作为输入,并且获取返回值的函数应持有对该输入的新引用。如果我使用C API编写此代码,则将其编写为:

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

PyO3中,浏览PyObjectPyObjectRef&PyObjectPy<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参数来手动增加引用计数,并使用生命周期批注使编译器满意,但我的印象是{{1} }打算使用对象生存期来管理引用计数本身

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

2 个答案:

答案 0 :(得分:5)

一个PyObjecta simple wrapper around a raw pointer

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

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

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

如果我们expand此代码:

#[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。通过调用from_borrowed_ptrimplemented for PyObject

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

同样,将PyObject放入Rust时,它将自动decrease the reference count。返回到Python后,ownership is transferred由Python代码决定是否适当地更新引用计数。


在master分支中为commit ed273982完成的所有调查,对应于v0.5.0-alpha.1。

答案 1 :(得分:4)

根据to the other answerpyo3,我们需要围绕我们的函数构建其他样板,以便跟踪Python参考计数。特别是,将对象作为参数传递给函数时,计数器已经递增。不过,clone_ref方法可用于显式创建对同一对象的新引用,这也会增加其引用计数器。

该函数的输出仍然必须是实际的Python对象,而不是对其的引用(这似乎是合理的,因为Python无法理解Rust引用; pyo3似乎忽略了这些函数中的生存期参数)。 / p>

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

从在Python领域中使用该功能(又不是一个严格的测试平台)开始,它至少达到了预期的效果。

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)