Написание функции 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)
Другие вопросы по тегам