PYO3是Python -> Rust / Rust-> Python的第三方库
参考的官方地址: https://pyo3.rs/main/module.html
利用pymodule宏创建模块:
use pyo3::prelude::*;
// 创建一个功能
#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}
/// 这个功能以函数的形式拓展在模块当中
#[pymodule]
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
// 拓展的函数名字就叫做double, m是拓展的自带参数
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
}
// 我们可以看一下这个过程宏的源码
#[proc_macro_attribute]
pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
parse_macro_input!(args as Nothing);
// 整体还是可以理解的,把我们输入的函数转换为ast, 并看看ast语法便以上有没有错误
let mut ast = parse_macro_input!(input as syn::ItemFn);
let options = match PyModuleOptions::from_attrs(&mut ast.attrs) {
Ok(options) => options,
Err(e) => return e.into_compile_error().into(),
};
if let Err(err) = process_functions_in_module(&mut ast) {
return err.into_compile_error().into();
}
// 贴心的位子自动写注释,不过我这里的注释打爆出来一直都是None, 不知道啥情况。
let doc = get_doc(&ast.attrs, None);
// 没有问题就把doc文档(我们Pycharm查看库的时候看到的内玩应一同打包。)
let expanded = pymodule_impl(&ast.sig.ident, options, doc, &ast.vis);
quote!(
#ast
#expanded
)
.into()
}
Pymodule肯定不止能打包函数,还可以打包类的,后面的部分会讲到。
Python模块可以分文件创建相应的内容,不过这一块太简单了,参考官网就可以了。
该#[pyfunction]
属性用于从 Rust 函数定义 Python 函数。还是一样的,我们随便看一下源码:
// 相较于上面的module, 这个宏就简单很多,当然我不可能取深究最低层的代码,太浪费时间了
#[proc_macro_attribute]
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
// 把我们的Fn 强制转换成PyFuction类型,然后把就强转
let mut ast = parse_macro_input!(input as syn::ItemFn);
let options = parse_macro_input!(attr as PyFunctionOptions);
let expanded = build_py_function(&mut ast, options).unwrap_or_compile_error();
// 这个宏强转TokenStream
quote!(
#ast
#expanded
)
.into()
}
根据官方文档,我们可以字此基础上做如下操作:
// 重命名
#[pyfunction]
#[pyo3(name = "test2")]
pub fn b(){
println!("Hello I am Lihua")
}
import rust_give_python as ru
if __name__ == '__main__':
ru.test2()
Hello I am Lihua
由于Rust的多线程是真正的多线程,所以我们很想使用类似于闭包的方法让Rust中运行Python的代码,但是比较可惜的是目前, Rust 中的 s 和 Python 中的 callable之间没有转换。这个想法目前还是不可行的,真的是麻中麻。
除了函数,我们有时候还需要定义类来更进一步的对Python进行提速封装。官网是这么说的:公开了一组由 Rust 的 proc 宏系统提供支持的属性,用于将 Python 类定义为 Rust 结构。
这一节的宏更类似与修饰器,问题不大,我们直接开冲
定义类数据部分结构的宏。一般选择用结构体定义,也可以接受枚举类型,其他类型不行:
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
use syn::Item;
// 分语法解析分词并查看有没有错误
let item = parse_macro_input!(input as Item);
match item {
// 这两个地方用的不同语法分析函数,我把struct分析函数放在了下面
Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()),
Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()),
unsupported => {
syn::Error::new_spanned(unsupported, "#[pyclass] only supports structs and enums.")
.into_compile_error()
.into()
}
}
}
// struct的分析函数
fn pyclass_impl(
attrs: TokenStream,
mut ast: syn::ItemStruct,
methods_type: PyClassMethodsType,
) -> TokenStream {
// 用这个宏价检查结构参数语法
let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
// 强转py_class
let expanded = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error();
// 打包
quote!(
#ast
#expanded
)
.into()
}
类肯定不能只有结构体,还应该有相应的method方法(类里面的执行代码不叫函数叫方法!!)。来看源码:
pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
// 编译的时候给一个feature变量? 主要是看拓展的方法是否为多个
// 之后用pymethods_impl去做语法转换
let methods_type = if cfg!(feature = "multiple-pymethods") {
PyClassMethodsType::Inventory
} else {
PyClassMethodsType::Specialization
};
pymethods_impl(input, methods_type)
}
// 在这里就直接利用quote宏打包了,expand使用的构造pymethod的方法。针对源码非常感兴趣的化自己去看呗
fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl);
let expanded = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error();
quote!(
#ast
#expanded
)
.into()
}
#[new]
有类就要有实例化嘛, 还有一些常见的方法,不是说你放了Pymodule就能调用了。Rust 方法名称在这里并不重要,我们不是定义完方法就直接可以用Rust的方法名称调用的 所以Rust为我们提供了一些服务名称,首先就是我们实例化的__new__方法.
#[pyclass]
pub struct test_class{
pub name: String,
pub id:i32,
}
#[pymethods]
impl test_class {
#[new] //我们新建一个new方法让类可以在Python中被创建。
fn new() -> test_class{
test_class{name: String::from("hello"), id:1}
}
}
import rust_give_python as ru
if __name__ == '__main__':
ru.test_class()
#[new]
方法必须返回T: Into<PyClassInitializer<Self>>
or PyResult<T> where T: Into<PyClassInitializer<Self>>
。不过我们在构造new的时候一般返回对象本身嘛,不过这也是后话了
[#pyo3(get,set,name = “*”)]
对于我们定义好的类来说,虽然他内部的属性已经有了定义的值/ 分配的内存,可是我们刚刚也说过名字毫无意义。我们根本不值到它真正的属性名字叫什么,**而且没有实现__dict__魔法方法你找不到的,,**所以我们就需要用pyo3定义get, set简单的查值和设置值。我们还可以使用name字段位属性重新设定名字
#[pyclass]
pub struct test_class{
#[pyo3(get,set)]
pub name: String,
#[pyo3(get,name="iddd")]
pub id:i32,
}
import rust_give_python as ru
if __name__ == '__main__':
a = ru.test_class()
print(a.name)
a.name = "Hello"
print(a.iddd)
// 要想改变值必须要有set才可以
try:
a.iddd = 10
print("set ok")
print(a.iddd)
except:
print("can't set a.iddd")
hello
1
can't set a.iddd
同时上面的表达等价位#[getter] #[setter]
pub struct test_class{
#[pyo3(get,set)]
pub name: String,
#[pyo3(get,name="iddd")]
pub id:i32,
pub year:i32
}
#[pymethods]
impl test_class {
#[new]
fn new() -> test_class{
test_class{name: String::from("hello"), id:1,year:80}
}
#[getter]
fn get_year(&self) ->PyResult<i32>{
Ok(self.year)
}
#[setter]
fn set_year(&mut self, value:i32){
self.year = value;
}
import rust_give_python as ru
if __name__ == '__main__':
a = ru.test_class()
a.year = 10
print(a.year)
10
除了参数之外,类还需要提供其他方法。classmethod为自定义类创建类方法, 不过我一般不咋用,官网的例子在这:
// 这个例子真的看起来没啥用就不演示
#[pymethods]
impl MyClass {
#[classmethod]
fn cls_method(cls: &PyType) -> PyResult<i32> {
Ok(10)
}
}