简体   繁体   English

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

[英]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中 ,我发现导航PyObjectPyObjectRef &PyObjectPy<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM