[英]Writing a pyo3 function equivalent to a Python function that returns its input object
I am looking to write a Rust backend for my library, and I need to implement the equivalent of the following function in pyo3
: 我想为我的库编写一个Rust后端,我需要在
pyo3
实现以下函数的等价物:
def f(x):
return x
This should return the same object as the input, and the function getting the return value should hold a new reference to the input. 这应该返回与输入相同的对象 ,获取返回值的函数应该包含对输入的新引用。 If I were writing this in the C API I would write it as:
如果我在C API中写这个,我会把它写成:
PyObject * f(PyObject * x) {
Py_XINCREF(x);
return x;
}
In PyO3 , I find it quite confusing to navigate the differences between PyObject
, PyObjectRef
, &PyObject
, Py<PyObject>
, Py<&PyObject>
. 在PyO3中 ,我发现导航
PyObject
, PyObjectRef
&PyObject
, Py<PyObject>
, Py<&PyObject>
之间的差异非常困惑。
The most naive version of this function is: 这个函数最天真的版本是:
extern crate pyo3;
use pyo3::prelude::*;
#[pyfunction]
pub fn f(_py: Python, x: &PyObject) -> PyResult<&PyObject> {
Ok(x)
}
Among other things, the lifetimes of x
and the return value are not the same, plus I see no opportunity for pyo3
to increase the reference count for x
, and in fact the compiler seems to agree with me: 除此之外,
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`
There may be a way for me to manually increase the reference count using the _py
parameter and use lifetime annotations to make the compiler happy, but my impression is that pyo3
intends to manage reference counts itself using object lifetimes. 我可以使用
_py
参数手动增加引用计数,并使用生命周期注释使编译器满意,但我的印象是pyo3
打算使用对象生存期来管理引用计数本身 。
What is the proper way to write this function? 编写此函数的正确方法是什么? Should I be attempting to wrap it in a
Py
container? 我应该尝试将其包装在
Py
容器中吗?
A PyObject
is a simple wrapper around a raw pointer : PyObject
是原始指针的简单包装器 :
pub struct PyObject(*mut ffi::PyObject);
It has multiple creation functions, each corresponding to different kinds of pointers that we might get from Python. 它有多个创建函数,每个函数对应于我们可能从Python获得的不同类型的指针。 Some of these, such as
from_borrowed_ptr
, call Py_INCREF
on the passed-in pointer. 其中一些,例如
from_borrowed_ptr
,在传入的指针上调用Py_INCREF
。
Thus, it seems like we can accept a PyObject
, so long as it was created in the "right" manner. 因此,似乎我们可以接受
PyObject
,只要它是以“正确”的方式创建的。
If we expand this code: 如果我们扩展此代码:
#[pyfunction]
pub fn example(_py: Python, x: PyObject) -> PyObject {
x
}
We can see this section of code that calls our function: 我们可以看到调用我们函数的代码段:
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,
))
},
)
Our argument is created by a call to ObjectProtocol::extract
, which in turn calls FromPyObject::extract
. 我们的参数是通过调用
ObjectProtocol::extract
创建的,而ObjectProtocol::extract
又调用FromPyObject::extract
。 This is implemented for PyObject
by calling from_borrowed_ptr
. 这是通过调用
from_borrowed_ptr
为PyObject
实现的 。
Thus, using a bare PyObject
as the argument type will correctly increment the reference count. 因此,使用裸
PyObject
作为参数类型将正确地增加引用计数。
Likewise, when a PyObject
is dropped in Rust, it will automatically decrease the reference count . 同样,当在Rust中删除
PyObject
时,它将自动减少引用计数 。 When it is returned back to Python, ownership is transferred and it is up to the Python code to update the reference count appropriately. 当它返回到Python时, 将转移所有权 ,并由Python代码来适当地更新引用计数。
All investigation done for commit ed273982 from the master branch, corresponding to v0.5.0-alpha.1. 所有调查都是从master分支提交ed273982 ,对应于v0.5.0-alpha.1。
According to the other answer , pyo3
takes care of building additional boilerplate around our functions in order to keep track of Python reference counting. 根据另一个答案 ,
pyo3
负责围绕我们的函数构建额外的样板,以便跟踪Python引用计数。 In particular, the counter is already incremented when passing the object as an argument to the function. 特别是,当将对象作为参数传递给函数时,计数器已经递增。 Nevertheless, the
clone_ref
method can be used to explicitly create a new reference to the same object, which will also increment its reference counter. 尽管如此,
clone_ref
方法可用于显式创建对同一对象的新引用,这也将增加其引用计数器。
The output of the function must still be an actual Python object rather than a reference to it (which seems reasonable, as Python does not understand Rust references; pyo3
seems to ignore lifetime parameters in these functions). 函数的输出必须仍然是一个实际的Python对象而不是对它的引用(这似乎是合理的,因为Python不理解Rust引用;
pyo3
似乎忽略了这些函数中的生命周期参数)。
#[pyfunction]
fn f(py: Python, x: PyObject) -> PyResult<PyObject> {
Ok(x.clone_ref(py))
}
From playing around with the function in Python land (AKA not a serious testbed), it at least seems to work as intended. 从使用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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.