I'm trying to understand the way references work in PyO3 when accepting a pyclass
as a function argument:
#[pyclass]
struct Number {
#[pyo3(get, set)]
value: i32,
}
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Number { value }
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!("Number({})", self.value))
}
}
#[pyfunction]
fn increment_1(n: &mut Number>) {
b.value += 1;
}
#[pyfunction]
fn increment_2(n: Bound<'_, Number>) {
n.borrow_mut().value += 1;
}
#[pyfunction]
fn increment_3(n: &Bound<'_, Number>) {
n.borrow_mut().value += 1;
}
#[pymodule]
fn mod(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Number>()?;
m.add_function(wrap_pyfunction!(increment_1, m)?)?;
m.add_function(wrap_pyfunction!(increment_2, m)?)?;
m.add_function(wrap_pyfunction!(increment_3, m)?)?;
Ok(())
}
The three version of increment_
accept a reference to Number
- one as a regular reference and the other two using Bound
.
They all seem to work:
from mod import Number, increment_1, increment_2, increment_3
n = Number(1)
print(n) # Number(1)
increment_1(n)
print(n) # Number(2)
increment_2(n)
print(n) # Number(3)
increment_3(n)
print(n) # Number(4)
So what's the difference between the versions? Which one is preferred?
Taking direct references is deprecated, due to being unsound and less performant. It is gated behind the gil-refs
feature, which is supposed to be added temporarily as you upgrade your PyO3 version from an old one and then removed when you finish migration of all code to Bound
.
It is going to be removed in the next major PyO3 version.
For more information see this github issue.
&Bound
is just a bit more efficient as it doesn't need to bump the refcount.
Bound
on the other hand is more convenient when you need an owned Bound
.