pyo3 使用说明

 

安装 PyO3 rust 调用 python

  • 安装 PyO3
  • rust 调用 python

安装 PyO3

  1. 安装 python

     env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.10-dev
    
  2. 创建并进入虚拟环境

     pyenv virtualenv 3.10-dev pyo3
     pyenv activate pyo3 
    
  3. 指定动态库

     export LD_LIBRARY_PATH=/home/wilson/.pyenv/versions/3.10-dev/lib/:/lib:/usr/lib:/usr/local/lib
    
  4. 创建 rust 工程

     cargo new demo
    
    • 设置 Cargo.toml

        [dependencies.pyo3]
        version = "0.17.3"
        features = ["auto-initialize"]
      
    • main.rs

        use pyo3::prelude::*;
        use pyo3::types::IntoPyDict;
      
        fn main() -> PyResult<()> {
            Python::with_gil(|py| {
                let sys = py.import("sys")?;
                let version: String = sys.getattr("version")?.extract()?;
      
                let locals = [("os", py.import("os")?)].into_py_dict(py);
                let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
                let user: String = py.eval(code, None, Some(&locals))?.extract()?;
      
                println!("Hello {}, I'm Python {}", user, version);
                Ok(())
            })
        }
      
  5. 运行

     cargo run
    

rust 调用 python

调用 python 函数

  • PyO3提供了多个api来进行函数调用
    • 接受args和kwargs参数的API
      • call 调用任何可调用的Python对象
      • call_method 调用Python对象上的方法
    • 简单调用的API
    • 为了方便起见,Py<T> 智能指针公开了这六个API方法,但需要一个Python令牌作为额外的第一个参数来证明GIL是存在的
  • 调用 python 函数

      use pyo3::prelude::*;
      use pyo3::types::PyTuple;
    
      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 != ():
                          print('调用位置参数', args)
                      if kwargs != {}:
                          print('调用字典参数', kwargs)
                      if args == () and kwargs == {}:
                          print('调用无参函数')",
                  "",
                  "",
              )?
              .getattr("example")?
              .into();
    
              fun.call0(py)?;
    
              let args = PyTuple::new(py, &[arg1, arg2, arg3]);
              fun.call1(py, args)?;
    
              let args = (arg1, arg2, arg3);
              fun.call1(py, args)?;
    
              Ok(())
          })
      }
    
    • 执行结果

        $ cargo run
        调用无参函数
        调用位置参数 ('arg1', 'arg2', 'arg3')
        调用位置参数 ('arg1', 'arg2', 'arg3')
      
  • 创建关键字参数
    • 关键字参数可以是None或Some(&PyDict)
    • 可以通过 IntoPyDict 特性将其他类字典容器(HashMap, BTreeMap)转换为字典
    • 可以转换最多 10 个元素的元组和Vec,每个元素是两个elem的元组
      use pyo3::prelude::*;
      use pyo3::types::IntoPyDict;
      use std::collections::HashMap;
    
      fn main() -> PyResult<()> {
          let key1 = "key1";
          let val1 = 1;
          let key2 = "key2";
          let val2 = 2;
    
          Python::with_gil(|py| {
              let fun: Py<PyAny> = PyModule::from_code(
                  py,
                  "def example(*args, **kwargs):
                      if args != ():
                          print('调用位置参数', args)
                      if kwargs != {}:
                          print('调用字典参数', kwargs)
                      if args == () and kwargs == {}:
                          print('调用无参函数')",
                  "",
                  "",
              )?
              .getattr("example")?
              .into();
    
              fun.call0(py)?;
    
              let kwargs = [(key1, val1)].into_py_dict(py);
              fun.call(py, (), Some(kwargs))?;
    
              let kwargs = vec![(key1, val1), (key2, val2)];
              fun.call(py, (), Some(kwargs.into_py_dict(py)))?;
    
              let kwargs = HashMap::from([(key1, 1)]);
              fun.call(py, (), Some(kwargs.into_py_dict(py)))?;
    
              Ok(())
          })
      }
    
    • 执行结果

        $ cargo run
        调用无参函数
        调用字典参数 {'key1': 1}
        调用字典参数 {'key1': 1, 'key2': 2}
        调用字典参数 {'key1': 1}
      

访问 python API

  • 通过 PyModule::import 获取Python模块的句柄
  • 它可以导入和使用环境中可用的任何Python模块
  • 调用内建模块

      use pyo3::prelude::*;
    
      fn main() -> PyResult<()> {
          Python::with_gil(|py| {
              let builtins = PyModule::import(py, "builtins")?;
              let total: i32 = builtins
                  .getattr("sum")?
                  .call1((vec![1, 2, 3],))?
                  .extract()?;
              println!("total: {total}");
    
              Ok(())
          })
      }
    
  • 运行一个表达式,可以使用 eval

      use pyo3::prelude::*;
    
      fn main() -> PyResult<()> {
          Python::with_gil(|py| {
              let result = py
                  .eval("[i * 10 for i in range(5)]", None, None)
                  .map_err(|e| {
                      e.print_and_set_sys_last_vars(py);
                  })
                  .unwrap();
              let res: Vec<i64> = result.extract().unwrap();
              println!("result: {res:?}");
              Ok(())
          })
      }
    

    执行结果

      result: [0, 10, 20, 30, 40]
    
  • 执行多个 python 语句
    • [Python::run](https://docs.rs/pyo3/0.17.3/pyo3/struct.Python.html#method.run是执行一个或多个 Python 语句的方法
    • 这个方法不返回任何内容(就像任何 Python 语句一样) ,但是您可以通过区域变量 dict 访问受操作的对象。
    • 你也可以使用 py_run!宏,这是Python::run的缩写。由于 py_run!异常时会 panic,我们建议您只使用此宏快速测试您的Python扩展
      use pyo3::prelude::*;
      use pyo3::{py_run, PyCell};
    
      #[pyclass]
      struct UserData {
          id: u32,
          name: String,
      }
    
      #[pymethods]
      impl UserData {
          fn as_tuple(&self) -> (u32, String) {
              (self.id, self.name.clone())
          }
    
          fn __repr__(&self) -> PyResult<String> {
              Ok(format!("User {}(id: {})", self.name, self.id))
          }
      }
    
      fn main() -> PyResult<()> {
          Python::with_gil(|py| {
              let userdata = UserData {
                  id: 34,
                  name: "wilson".to_string(),
              };
              let userdata = PyCell::new(py, userdata).unwrap();
              let userdata_as_tuple = (34, "wilson");
              py_run!(py, userdata userdata_as_tuple, r#"
              print((userdata))
              print(userdata.as_tuple())
              "#);
              Ok(())
          })
      }
    

    执行结果

      User wilson(id: 34)
      (34, 'wilson')
    
  • 使用 pytho 的 context manager

    • 通过直接调用 __enter____exit__ 使用context 管理器

        use pyo3::prelude::*;
        use pyo3::types::PyModule;
      
        fn main() {
            Python::with_gil(|py| {
                let custom_manager = PyModule::from_code(
                    py,
                    r#"
        class House(object):
            def __init__(self, address):
                self.address = address
            def __enter__(self):
                print(f"欢迎来到 [{self.address}]!")
            def __exit__(self, type, value, traceback):
                if type:
                    print(f"对不请,你的 [{self.address}] 有问题")
                else:
                    print(f"感谢您参观 [{self.address}],下次再见")
      
                "#,
                    "house.py",
                    "house",
                )
                .unwrap();
      
                let house_class = custom_manager.getattr("House").unwrap();
                let house = house_class.call1(("123 大街",)).unwrap();
      
                house.call_method0("__enter__").unwrap();
      
                let result = py.eval("undefined_variable + 1", None, None);
      
                // 如果 eval 抛出异常,它会被 context manager 抓住
                // 否则,__exit__ 的参数是 None
                match result {
                    Ok(_) => {
                        let none = py.None();
                        house
                            .call_method1("__exit__", (&none, &none, &none))
                            .unwrap();
                    }
                    Err(e) => {
                        house
                            .call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback(py)))
                            .unwrap();
                    }
                }
            })
        }
      

      执行结果

        欢迎来到 [123 大街]!
        对不请,你的 [123 大街] 有问题
      

Python 调用 Rust

  • 创建 python 虚拟环境

      $ mkdir string_sum
      $ cd string_sum
      $ python -m venv .env
      $ source .env/bin/activate
      $ pip install maturin
    
  • 创建 rust 工程

      $ maturin init
      ✔ 🤷 What kind of bindings to use? · pyo3
      ✨ Done! New project created string_sum