类型转换:
与使用 Python 原生类型相比,使用 Rust 库类型作为函数参数会产生转换成本。使用 Python 原生类型几乎是零成本(它们只需要类似于 Python 内置函数的类型检查isinstance()
)。
能转换的类型比较多,都是常见基础类型不一一列举:https://pyo3.rs/main/conversions/tables.html
PyO3 提供了一些方便的特征来在 Python 类型和 Rust 类型之间进行转换。
extract/ FromPyObject
将Python对象转换成rust最简单的方法就是使用extract. 其实extract这个trait就来源于FromPyObject这个trait实现的:
pub fn extract<'a, D>(&'a self) -> PyResult<D>
where
D: FromPyObject<'a>, {FromPyObject::extract(self)}
官方书上的例子确实跑不起来,看我的例子吧:
#[pyfunction]
pub fn test_trans(list:&PyList){
let v: Vec<i32> = list.extract().unwrap();
println!("Change is Ok {:?}", v);
}
// 这里没写在模块中注册,自己记得写一下
>>> from rust_give_python import *
>>> p = [1,2,3,4,5]
>>> test_trans(p)
Change is Ok [1, 2, 3, 4, 5]
>>> p = ['1','2','3','4','5']
>>> test_trans(p)
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: PyErr { type: <class 'TypeError'>, value: TypeError("'str' object cannot be interpreted as an integer"), traceback: None }', src\lib.rs:166:38
此外FromPyObject可以实现结构派生:派生代码将调用getattr方法获取响应属性,并最后使用extract方法转换:
#[derive(FromPyObject)]
pub struct tests_class_struct{
pub string_test:String,
}
#[pyfunction]
pub fn trans_class(class_1:&PyAny){
let q:tests_class_struct = class_1.extract().unwrap();
println!("Translate_Ok");
}
// 同样的把注册的部分省略了
>>> class a:
... def __init__(self):
... self.string_test = "hello world"
>>> p = a()
>>> from rust_give_python import *
>>> trans_class(p)
Translate_Ok
我们也可以添加#[pyo3(item)]
字段上设置属性,PyO3 将尝试通过调用get_item
Python 对象上的方法来提取值。这里就不作演示
关于derive(FromPyObject)的使用小贴士:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dinwn07U-1652888146101)(C:\Users\40629\Pictures\pyo3.png)]
IntoPy<T>
和上面Python转Rust的类型不同,这个类型就是为了Rust转Python用的,但实际上对于我们自定一的类型和方法用Pyclass其实绰绰有余的。换句话说我们基本的rust类型都实现了这个Trait, 而平时也不会直接调用而是调用它的高级分装版本,比如#[pyclass]
从Rust调用Python
任何 Python 原生对象引用(例如&PyAny
、&PyList
或&PyCell<MyClass>
)都可用于调用 Python 函数。(当然,我感觉最常用的还是&PyAny) .
PyO3提供了两个接口用例调用Python的可调用对象:函数/对象的可调用方法:
- call- 调用任何可调用的 Python 对象。接受args和kwards作为参数接口
- call_method- 在 Python 对象上调用方法。接受args和kwards作为参数接口
-
call1
&call_method1
只接受args参数 -
call0
&call_method2
不接受参数调用函数。
例子:
fn main() -> PyResult<()> {
let arg1 = "arg1";
let arg2 = "arg2";
let arg3 = "arg3";
Python::with_gil(|py| {
let fun: Py<PyAny> = PyModule::from_code(
py,
"def example(*args, **kwargs):
if args != ():
return 1
if kwargs != {}:
return 2
if args == () and kwargs == {}:
return 0",
"",
"",
)?.getattr("example")?.into();
// call object without empty arguments
let p = fun.call0(py).unwrap();
println!("{}",p);
// call object with PyTuple
let args = PyTuple::new(py, &[arg1, arg2, arg3]);
let p: PyObject = fun.call1(py, args)?;
println!("{}",p);
// pass arguments as rust tuple
let args = ();
let mut kwards = PyDict::new(py);
kwards.set_item("1","2");
let p = fun.call(py, args, Option::from(kwards))?;
println!("{}",p);
Ok(())
})
}
0
1
2
当然,对于现有的引入包的文件也是可以跑的:
fn main() {
// 这里我填写的绝对路径
let py_foo = include_str!(".../import_try.py");
Python::with_gil(|py| {
// 这个函数涉及到导入requests直接跑了
let fuc = PyModule::from_code(py,py_foo,"","").unwrap().getattr("requests_try").unwrap();
let q = fuc.call0().unwrap();
println!("{}", q);
});
}
200
# import_try.py
import requests
def requests_try():
m = requests.get("https://www.baidu.com")
return m.status_code
if __name__ == '__main__':
print(requests_try())
需要注意的是,我们在Python::这个域里面写的东西域返回的东西的类型都在GIL锁的生命周期内,出GIL之后就无法使用
let a = Python::with_gil(|py| {
...
let q = fuc.call0().unwrap();
q })
^ returning this value requires that `'1` must outlive `'2`
这样的传参是不可以执行的。这个时候我们就可以使用extract来强制类型转换了
let h:i32 = q.extract().unwrap();
h )}
当然。关于GIL生命周期域可变类型可以参考 官网。这里只需要知道我们的结果想要在Rust代码中使用需要上一节的类型转换即可
关于GIL光放文档给出了这样的说法:全局解释器锁 (GIL) 对这种情况有所帮助,它确保只有一个线程可以同时使用 Python 解释器及其 API,而非 Python 操作(系统调用和扩展代码)可以解锁 GIL 。具体的含义将在下一节做验证.
ps.这两天在调GTK环境最后放弃了,之后应该会走fltk路线,开发最有成就感的不就是GUI么