我有以下函数,它使用PyO3调用python函数并获得结果(在本例中,将int
分配给i32
):
fn run_python<'a, T: FromPyObject<'a> + Clone>(func_name: &str) -> Result<T, ()> {
Python::with_gil(|py| {
let pyapi = match py.import("pyapi") {
Ok(v) => v,
Err(e) => { e.print_and_set_sys_last_vars(py); return Err(()) },
};
let locals = [("pyapi", pyapi)].into_py_dict(py);
let eval_result: PyResult<&PyAny> = py.eval("pyapi.{}(**kwargs)", None, Some(&locals));
let wrapped_obj: &PyAny = match eval_result {
Ok(v) => v,
Err(e) => { e.print_and_set_sys_last_vars(py); return Err(()) },
};
let unwrapped_result: PyResult<T> = wrapped_obj.extract();
match unwrapped_result {
Ok(v) => return Ok(v.clone()),
Err(e) => return Err(()),
};
})
}
当我尝试编译时,我得到以下错误:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'p` due to conflicting requirements
--> srcbinlaunch.rs:89:30
|
89 | let eval_result = py.eval("pyapi.{}(**kwargs)", None, Some(&locals));
| ^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 82:22...
--> srcbinlaunch.rs:82:22
|
82 | Python::with_gil(|py| {
| ______________________^
83 | | let pyapi = match py.import("pyapi") {
84 | | Ok(v) => v,
85 | | Err(e) => { e.print_and_set_sys_last_vars(py); return Err(()) },
... |
101 | | };
102 | | })
| |_____^
note: ...so that the types are compatible
--> srcbinlaunch.rs:89:30
|
89 | let eval_result = py.eval("pyapi.{}(**kwargs)", None, Some(&locals));
| ^^^^
= note: expected `pyo3::Python<'_>`
found `pyo3::Python<'_>`
note: but, the lifetime must be valid for the lifetime `'a` as defined on the function body at 81:15...
--> srcbinlaunch.rs:81:15
|
81 | fn run_python<'a, T: FromPyObject<'a> + Clone>(func_name: &str) -> Result<T, ()> {
| ^^
note: ...so that the types are compatible
--> srcbinlaunch.rs:96:57
|
96 | let unwrapped_result: PyResult<T> = wrapped_obj.extract();
| ^^^^^^^
= note: expected `pyo3::FromPyObject<'_>`
found `pyo3::FromPyObject<'a>`
我是Rust新手,可能正在做一些愚蠢的事情(很可能是X/Y问题)。我怎么能得到一个值的py.eval
,没有一个生命周期绑定到python解释器(这是我假设是在这里发生的)?
Ry建议的修复似乎工作得很好。使用for<'p>
可以让编译器推迟生命周期的计算,直到处理调用.extract()
的代码时才需要。并且生存期'p
不需要在函数的泛型参数列表中指定。此外,Clone
绑定也不是必需的。
fn run_python<T>(func_name: &str) -> Result<T, ()>
where
T: for<'p> FromPyObject<'p>,
{
let guard = Python::acquire_gil();
let py = guard.python();
match || -> _ { // try...
let pyapi = py.import("pyapi")?; // throw...
let locals = [("pyapi", pyapi)].into_py_dict(py);
py.eval(&format!("pyapi.{}(**kwargs)", func_name),
None,
Some(&locals))?.extract()
}() { // catch...
Err(e) => {
// Error handling specific to Pyo3.
e.print_and_set_sys_last_vars(py);
// Return the error type spec'd in the fn signature.
Err(())
},
Ok(obj) => Ok(obj),
}
}
这种方法的唯一限制是.extract()
转换为的类型不需要依赖于它所转换的类型的生存期。例如,如果run_python()
可以返回字符串列表,这是可能的:
let values: Vec<String> = run_python("make_string_list").unwrap(); // OK.
但是这会产生与生命周期相关的编译错误,尽管.extract()
能够在适当的条件下产生该类型:
let values: Vec<&str> = run_python("make_string_list").unwrap(); // Error.
如果run_python()
需要能够产生依赖于生命周期的值,那么一个解决方案可能是调用者获取GIL,并传递一个Python
实例。函数可以像这样:
fn run_python<'p, T>(func_name: &str, py: Python<'p>) -> PyResult<T>
where
T: FromPyObject<'p>,
{
let pyapi = py.import("pyapi")?;
let locals = [("pyapi", pyapi)].into_py_dict(py);
py.eval(&format!("pyapi.{}(**kwargs)", func_name),
None,
Some(&locals))?.extract()
}
在我意识到使用for<>
的建议是最好的选择之前,我开始写另一个答案。我假设返回值有一些依赖于GIL,但.extract()
返回类型不依赖于GIL。
在前面的回答中,我提出了处理需要在GIL生命周期之后保存的Python对象的方法。这涉及到使用.to_object(py)
将吉尔依赖类型转换为吉尔独立类型,并在需要时再次使用.cast_as::<PyType>(py)
等方法。