当前位置: 首页 > 工具软件 > PyO3 > 使用案例 >

Pyo3学习,尝试对Python加速

倪风史
2023-12-01

PYO3是Python -> Rust / Rust-> Python的第三方库

参考的官方地址: https://pyo3.rs/main/module.html

创建Python模块(打包)

利用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模块可以分文件创建相应的内容,不过这一块太简单了,参考官网就可以了。

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 类

除了函数,我们有时候还需要定义类来更进一步的对Python进行提速封装。官网是这么说的:公开了一组由 Rust 的 proc 宏系统提供支持的属性,用于将 Python 类定义为 Rust 结构。

这一节的宏更类似与修饰器,问题不大,我们直接开冲

#[Pyclass]

定义类数据部分结构的宏。一般选择用结构体定义,也可以接受枚举类型,其他类型不行:

#[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()
}

#[pymethods]

类肯定不能只有结构体,还应该有相应的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]

除了参数之外,类还需要提供其他方法。classmethod为自定义类创建类方法, 不过我一般不咋用,官网的例子在这:

// 这个例子真的看起来没啥用就不演示
#[pymethods]
impl MyClass {
    #[classmethod]
    fn cls_method(cls: &PyType) -> PyResult<i32> {
        Ok(10)
    }
}
 类似资料: