rust 用pyo3实现闭包的装饰器

hivapdat  于 2023-02-04  发布在  其他
关注(0)|答案(2)|浏览(210)

作为一个学习练习,我尝试使用闭包在pyo3中实现一个参数化的装饰器函数。(非参数化的)装饰器被实现为具有__call__方法的类,而我我在此基础上构建并创建了一个参数化装饰器,它使用一个外部类和一个__call__方法,该方法返回一个内部类和一个__call__,该内部类调用目标函数,并且它工作了。但是作为一个学习练习(我需要特别提高我对生命周期的理解),我想尝试在闭包方面实现同样的事情。(注意我以前在C++中用lambdas做过)
因此,我的非参数化装饰器,经过一些试验和与编译器的斗争,看起来像这样:

#[pyfunction]
pub fn exectime(py: Python, wraps: PyObject) -> PyResult<&PyCFunction> {
    PyCFunction::new_closure(
        py, None, None,
        move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
            Python::with_gil(|py| {
                let now = Instant::now();
                let ret = wraps.call(py, args, kwargs);
                println!("elapsed (ms): {}", now.elapsed().as_millis());
                ret
            })
        }
    )
}

注意我需要将捕获的py Package 在Python::with_gil中才能使其工作。尝试将其扩展到我提出的嵌套装饰器:

#[pyfunction]
pub fn average_exectime(py: Python, n: usize) -> PyResult<&PyCFunction> {
    let f = move |args: &PyTuple, _kwargs: Option<&PyDict>| -> PyResult<&PyCFunction> {
        Python::with_gil(|py| {
            let wraps: PyObject = args.get_item(0)?.into();
            let g = move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
                Python::with_gil(|py| {
                    let now = Instant::now();
                    for _ in 0..n-1 {
                        wraps.call(py, args, kwargs);
                    }
                    let ret = wraps.call(py, args, kwargs);
                    println!("elapsed (ms): {}", now.elapsed().as_millis());
                    ret
                })
            };
            PyCFunction::new_closure(py, None, None, g)
        })
    };
    PyCFunction::new_closure(py, None, None, f)
}

编译器告诉我:

error: lifetime may not live long enough ] 44/45: poetry-rust-integration
  --> src/decorator.rs:48:13
   |
35 |         Python::with_gil(|py| {
   |                           --- return type of closure is Result<&'2 pyo3::types::PyCFunction, pyo3::PyErr>
   |                           |
   |                           has type `pyo3::Python<'1>`
...
48 |             PyCFunction::new_closure(py, None, None, g)
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`

    Building [=========================> ] 44/45: poetry-rust-integration                                                                                                                              error: aborting due to previous error

我尝试了各种各样的生存期参数,包括封闭的生存期都没有用,我只是得到了更多的错误。我想我不明白为什么编译器认为内部的生存期必须比另一个长?告诉编译器它们有相同的生存期还不够吗?如果是这样,如何实现?

uurity8g

uurity8g1#

pub fn new_closure<'a, F, R>(
    py: Python<'a>,
    name: Option<&'static str>,
    doc: Option<&'static str>,
    closure: F
) -> PyResult<&'a PyCFunction>

因此,您得到的&PyCFunction仅对with_gil块的范围有效。
要使函数比GIL块更有效,需要将其转换为a GIL-independent reference

ctrmrzij

ctrmrzij2#

非常感谢Masklinn解释了这个错误并为我指出了正确的方向。我现在有了这个可行的解决方案:

#[pyfunction]
pub fn average_exectime(py: Python, n: usize) -> PyResult<&PyCFunction> {
    let f = move |args: &PyTuple, _kwargs: Option<&PyDict>| -> PyResult<Py<PyCFunction>> {
        Python::with_gil(|py| {
            let wraps: PyObject = args.get_item(0)?.into();
            let g = move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
                Python::with_gil(|py| {
                    let now = Instant::now();
                    let mut result: PyObject = py.None();
                    for _ in 0..n {
                        result = wraps.call(py, args, kwargs)?;
                    }
                    println!("elapsed (ms): {}", now.elapsed().as_millis());
                    Ok(result)
                })
            };
            match PyCFunction::new_closure(py, None, None, g) {
                Ok(r) => Ok(r.into()),
                Err(e) => Err(e)
            }
        })
    };
    PyCFunction::new_closure(py, None, None, f)
}

相关问题