如何用Rust中的可变参数覆盖Python中的另一个方法?

nkkqxpd9  于 2023-08-05  发布在  Python
关注(0)|答案(1)|浏览(113)

我有两个struct:结构和内部结构。Struct有两个方法:使用modify_object_innermodify_objectmodify_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
如果有人知道答案,请另外发布您的知识来源(例如链接到文档的适当部分),因为我很迷茫,不知道如何找到答案。

68bkxrlz

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(),因此该方法会出现混乱。
有两种可能的解决方案:

  • 切勿在“生 rust ”中使用此方法。总是在Python中实现它,只是去掉Rust实现。如果Python用户不实现它,则会抛出异常。
  • 请先卸除borrow_mut(),再呼叫方法。就像这样:
let guard = this.borrow_mut();
let inner_struct = &mut guard.inner_struct;
let kwargs = PyDict::new(py);
kwargs
    .set_item("object_to_modify", inner_struct)
    .expect("Error with set_item");
drop(guard);
this.call_method("modify_object_inner", (), Some(kwargs))
    .expect("Error with call_method");

字符串
在这里,当您呼叫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代码:

use pyo3::prelude::*;
use pyo3::types::PyDict;

#[pyclass(subclass)]
pub struct InnerStruct {
    #[pyo3(get, set)]
    pub field: i32,
}

#[pyclass(subclass)]
pub struct Struct {
    #[pyo3(get, set)]
    pub inner_struct: Py<InnerStruct>,
}

#[pymethods]
impl InnerStruct {
    #[new]
    fn new(field: i32) -> Self {
        InnerStruct { field }
    }
}

#[pymethods]
impl Struct {
    #[new]
    fn new(inner_struct: Py<InnerStruct>) -> Self {
        Struct { inner_struct }
    }

    fn modify_object(this: &PyCell<Self>, py: Python<'_>) -> PyResult<()> {
        let inner_struct = &this.borrow().inner_struct;
        let kwargs = PyDict::new(py);
        kwargs.set_item("object_to_modify", inner_struct)?;
        this.call_method("modify_object_inner", (), Some(kwargs))?;
        Ok(())
    }
    fn modify_object_inner(&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)  # Prints -1

class PythonListElement(Struct):
    def __new__(cls, inner_struct):
        return super().__new__(cls, inner_struct)

    # Notice the name! It should match the name of the keyword argument you're passing!
    def modify_object_inner(self, object_to_modify):
        object_to_modify.field = 1

inner_struct = InnerStruct(0)
# And here there was a typo, it should be `PythonListElement`, not `PythonStruct`!
ps = PythonListElement(inner_struct)
ps.modify_object()
print(ps.inner_struct.field) # Prints 1

相关问题