Написание функции pyo3, эквивалентной функции Python, которая возвращает свой входной объект
Я ищу написать Rust backend для моей библиотеки, и мне нужно реализовать эквивалент следующей функции в pyo3
:
def f(x):
return x
Это должно вернуть тот же объект, что и вход, и функция, получающая возвращаемое значение, должна содержать новую ссылку на вход. Если бы я писал это в C API, я бы написал так:
PyObject * f(PyObject * x) {
Py_XINCREF(x);
return x;
}
В PyO3 я нахожу довольно запутанным ориентироваться в различиях между PyObject
, 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
контейнер?
2 ответа
PyObject
простая обёртка вокруг необработанного указателя:
pub struct PyObject(*mut ffi::PyObject);
Он имеет несколько функций создания, каждая из которых соответствует различным видам указателей, которые мы можем получить из Python. Некоторые из них, такие как from_borrowed_ptr
, вызов Py_INCREF
на переданном указателе.
Таким образом, кажется, что мы можем принять 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
, Это реализовано для PyObject
позвонив from_borrowed_ptr
,
Таким образом, используя голые PyObject
как тип аргумента будет правильно увеличивать счетчик ссылок.
Аналогично, когда PyObject
удаляется в Rust, это автоматически уменьшает счетчик ссылок. Когда он возвращается обратно в Python, право собственности передается, и это зависит от кода Python, чтобы соответствующим образом обновить счетчик ссылок.
Все исследования выполнены для коммита ed273982 из мастер ветки, соответствующего v0.5.0-alpha.1.
Согласно другому ответу, pyo3
заботится о создании дополнительных шаблонов для наших функций, чтобы отслеживать подсчет ссылок Python. В частности, счетчик уже увеличивается при передаче объекта в качестве аргумента функции. Тем не менее, clone_ref
Метод может быть использован для явного создания новой ссылки на тот же объект, который также увеличивает его счетчик ссылок.
Выходные данные функции должны все еще быть фактическим объектом Python, а не ссылкой на него (что кажется разумным, поскольку Python не понимает ссылки на Rust; pyo3
похоже, игнорирует параметры времени жизни в этих функциях).
#[pyfunction]
fn f(py: Python, x: PyObject) -> PyResult<PyObject> {
Ok(x.clone_ref(py))
}
Играя с функцией в Python Land (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)