rust 用pyo3实现闭包的装饰器

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

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

  1. #[pyfunction]
  2. pub fn exectime(py: Python, wraps: PyObject) -> PyResult<&PyCFunction> {
  3. PyCFunction::new_closure(
  4. py, None, None,
  5. move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
  6. Python::with_gil(|py| {
  7. let now = Instant::now();
  8. let ret = wraps.call(py, args, kwargs);
  9. println!("elapsed (ms): {}", now.elapsed().as_millis());
  10. ret
  11. })
  12. }
  13. )
  14. }

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

  1. #[pyfunction]
  2. pub fn average_exectime(py: Python, n: usize) -> PyResult<&PyCFunction> {
  3. let f = move |args: &PyTuple, _kwargs: Option<&PyDict>| -> PyResult<&PyCFunction> {
  4. Python::with_gil(|py| {
  5. let wraps: PyObject = args.get_item(0)?.into();
  6. let g = move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
  7. Python::with_gil(|py| {
  8. let now = Instant::now();
  9. for _ in 0..n-1 {
  10. wraps.call(py, args, kwargs);
  11. }
  12. let ret = wraps.call(py, args, kwargs);
  13. println!("elapsed (ms): {}", now.elapsed().as_millis());
  14. ret
  15. })
  16. };
  17. PyCFunction::new_closure(py, None, None, g)
  18. })
  19. };
  20. PyCFunction::new_closure(py, None, None, f)
  21. }

编译器告诉我:

  1. error: lifetime may not live long enough ] 44/45: poetry-rust-integration
  2. --> src/decorator.rs:48:13
  3. |
  4. 35 | Python::with_gil(|py| {
  5. | --- return type of closure is Result<&'2 pyo3::types::PyCFunction, pyo3::PyErr>
  6. | |
  7. | has type `pyo3::Python<'1>`
  8. ...
  9. 48 | PyCFunction::new_closure(py, None, None, g)
  10. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
  11. Building [=========================> ] 44/45: poetry-rust-integration error: aborting due to previous error

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

uurity8g

uurity8g1#

  1. pub fn new_closure<'a, F, R>(
  2. py: Python<'a>,
  3. name: Option<&'static str>,
  4. doc: Option<&'static str>,
  5. closure: F
  6. ) -> PyResult<&'a PyCFunction>

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

ctrmrzij

ctrmrzij2#

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

  1. #[pyfunction]
  2. pub fn average_exectime(py: Python, n: usize) -> PyResult<&PyCFunction> {
  3. let f = move |args: &PyTuple, _kwargs: Option<&PyDict>| -> PyResult<Py<PyCFunction>> {
  4. Python::with_gil(|py| {
  5. let wraps: PyObject = args.get_item(0)?.into();
  6. let g = move |args: &PyTuple, kwargs: Option<&PyDict>| -> PyResult<PyObject> {
  7. Python::with_gil(|py| {
  8. let now = Instant::now();
  9. let mut result: PyObject = py.None();
  10. for _ in 0..n {
  11. result = wraps.call(py, args, kwargs)?;
  12. }
  13. println!("elapsed (ms): {}", now.elapsed().as_millis());
  14. Ok(result)
  15. })
  16. };
  17. match PyCFunction::new_closure(py, None, None, g) {
  18. Ok(r) => Ok(r.into()),
  19. Err(e) => Err(e)
  20. }
  21. })
  22. };
  23. PyCFunction::new_closure(py, None, None, f)
  24. }
展开查看全部

相关问题