我有两个struct:结构和内部结构。Struct有两个方法:使用modify_object_inner
的modify_object
。modify_object_inner
的Rust实现并不重要,因为我想在Python中继承Struct的类中实现这个方法。函数modify_object
修改InnerStruct类型的Struct的字段。我做了这个代码,它编译:
use pyo3::prelude::*;
use pyo3::types::PyDict;
#[pyclass(subclass)]
#[derive(Clone)]
pub struct InnerStruct {
#[pyo3(get,set)]
pub field: i32
}
#[pyclass(subclass)]
pub struct Struct {
#[pyo3(get,set)]
pub inner_struct: InnerStruct
}
#[pymethods]
impl InnerStruct {
#[new]
fn new(field: i32) -> Self {
InnerStruct {field}
}
}
// I had to implement this because of error "error[E0277]: the trait bound `&mut InnerStruct: ToPyObject` is not satisfied"
impl ToPyObject for &mut InnerStruct {
fn to_object(&self, py: Python<'_>) -> PyObject {
let dict = PyDict::new(py);
dict.set_item("field", self.field).expect("Failed to set field in dictionary");
dict.into()
}
}
#[pymethods]
impl Struct {
#[new]
fn new(inner_struct: InnerStruct) -> Self {
Struct { inner_struct}
}
fn modify_object(this: &PyCell<Self>) -> () {
Python::with_gil(|py| {
let inner_struct = &mut this.borrow_mut().inner_struct;
let kwargs = PyDict::new(py);
kwargs.set_item("object_to_modify", inner_struct).expect("Error with set_item");
this.call_method("modify_object_inner", (), Some(kwargs)).expect("Error with call_method");
});
}
fn modify_object_inner(&mut self, object_to_modify: &mut InnerStruct) {
object_to_modify.field = -1
}
}
#[pymodule]
fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Struct>()?;
m.add_class::<InnerStruct>()?;
Ok(())
}
字符串
但是当我用这个Python代码测试它时:
from my_rust_module import InnerStruct, Struct
import os
os.environ['RUST_BACKTRACE'] = '1'
class PythonStruct(Struct):
def __new__(cls, inner_struct):
return super().__new__(cls, inner_struct)
inner_struct = InnerStruct(0)
ps = PythonStruct(inner_struct)
ps.modify_object()
print(ps.inner_struct.field) # without overwriting should print -1
class PythonListElement(Struct):
def __new__(cls, inner_struct):
return super().__new__(cls, inner_struct)
def modify_object_inner(self, inner_struct):
inner_struct.field = 1
inner_struct = InnerStruct(0)
ps = PythonStruct(inner_struct)
ps.modify_object()
print(ps.inner_struct.field) # without overwriting should print 1
型
我得到了:thread '<unnamed>' panicked at 'Error with call_method: PyErr { type: <class 'RuntimeError'>, value: RuntimeError('Already borrowed'), traceback: None }', src\lib.rs:46:71
个
如果有人知道答案,请另外发布您的知识来源(例如链接到文档的适当部分),因为我很迷茫,不知道如何找到答案。
1条答案
按热度按时间68bkxrlz1#
PyO3仍然需要保持Rust的借贷规则。它不能让你在Rust中同时拥有两个对同一个对象的可变引用。但在Python中,所有的引用都是可变的,那么它是如何做到这一点的呢?
答桉是
PyCell
它有点像RefCell
:动态跟踪借用状态的类型。当你调用一个Rust函数需要&mut self
或&self
时,PyO3会尝试借用PyCell
中的值(可变的或不变的),如果因为已经被借用而无法借用,则会引发异常。在
modify_object()
中,你取this: &PyCell<Self>
和borrow_mut()
它。当此参照处于活动状态时,您将无法再借用此对象。当调用modify_object_inner()
时,PyO3试图为&mut self
不可变地借用相同的对象,但失败了。因此,对call_method()
的调用失败,并且由于您在其上调用了expect()
,因此该方法会出现混乱。有两种可能的解决方案:
borrow_mut()
,再呼叫方法。就像这样:字符串
在这里,当您呼叫Rust方法时,对象就不再是可变借用的。
但是,您的代码有一个不相关的错误:您为
&mut InnerStruct
实现了ToPyObject
,但这段代码只是创建了一个字典。该指令与原始对象没有关联,并且任何一个的变异都不会影响另一个。您需要在结构中包含一个
Py<InnerStruct>
,而不仅仅是InnerStruct
。&Py
实现了ToPyObject
,因此您可以将其传递给Python并观察更改。您也不再需要
borrow_mut()
,而只需要borrow()
,如果您注意到在modify_object_inner()
中也只需要&self
(或者甚至只需要&PyCell<Self>
,这根本不借用对象),则可以保留modify_object_inner()
的Rust实现(因为允许两个共享引用共存)。另外两点是,您不需要
Python::with_gil()
,您可以(也应该)将py: Python<'_>
作为参数,PyO3将自动执行所有需要的操作,并且使用expect()
并惊慌失措是一个坏主意:您应该返回PyResult
并传播该错误。如果你惊慌,你会得到一个特殊的PanicException
,它不应该被抓住。但是如果您传播错误,就会得到一个正常的异常。虽然在字典集上出现混乱可能没什么问题(因为如果项不可散列,那么它就会失败,而它确实是可散列的),但您不希望在以下情况下出现混乱:有人删除了子类中的modify_object_inner()
方法,您需要一个可以被捕获的正常异常。以下是建议的Rust代码:
型
Python代码:
型