Skip to content

PyO3 Notes

1. Introduction to PyO3

PyO3 allows Rust code to interface with Python, enabling the creation of Python modules and embedding Python in Rust applications. It provides Rust bindings for the Python C API.

2. Setting Up PyO3

To use PyO3, add the following to Cargo.toml:

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }

For a binary application embedding Python:

[dependencies]
pyo3 = { version = "0.20" }

Enable the extension-module feature only when building a Python extension.

3. Creating a Python Module in Rust

Create a Rust library and add lib.rs:

use pyo3::prelude::*;

#[pyfunction]
fn add(a: i32, b: i32) -> PyResult<i32> {
    Ok(a + b)
}

#[pymodule]
fn mymodule(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(add, m)?)?;
    Ok(())
}

Compile it to a Python module using maturin:

maturin develop

Then use it in Python:

import mymodule
print(mymodule.add(2, 3))

4. Working with Python Objects in Rust

use pyo3::types::PyDict;
use pyo3::prelude::*;

#[pyfunction]
fn create_dict(py: Python) -> PyResult<PyObject> {
    let dict = PyDict::new(py);
    dict.set_item("key", "value")?;
    Ok(dict.into())
}

5. Calling Python from Rust

use pyo3::prelude::*;

fn main() {
    Python::with_gil(|py| {
        let sys = py.import("sys").unwrap();
        let version: String = sys.getattr("version").unwrap().extract().unwrap();
        println!("Python version: {}", version);
    });
}

6. Using Rust Structs in Python

use pyo3::prelude::*;

#[pyclass]
struct Person {
    #[pyo3(get, set)]
    name: String,
}

#[pymethods]
impl Person {
    #[new]
    fn new(name: String) -> Self {
        Person { name }
    }
}

#[pymodule]
fn mymodule(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Person>()?;
    Ok(())
}

7. Handling Python Exceptions in Rust

use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;

#[pyfunction]
fn fail() -> PyResult<()> {
    Err(PyValueError::new_err("An error occurred"))
}

8. Embedding Python in a Rust Binary

use pyo3::prelude::*;

fn main() {
    Python::with_gil(|py| {
        let locals = [("x", 5)].into_py_dict(py);
        py.run("print(x * 2)", None, Some(&locals)).unwrap();
    });
}

9. Async Python with Tokio and PyO3

use pyo3::prelude::*;
use pyo3_asyncio::tokio::future_into_py;
use tokio::time::{sleep, Duration};

#[pyfunction]
fn async_function(py: Python) -> PyResult<PyObject> {
    future_into_py(py, async move {
        sleep(Duration::from_secs(2)).await;
        Ok("Done!")
    })
}

10. Compiling and Using PyO3 Modules

  • Build the module: maturin build
  • Install locally: maturin develop
  • Run in Python: import mymodule