Pyo3 : Is there any way to cache once-loaded huge python module across functions?

Hi all!

I'm a complete Rust newbie, Please help me. :pray:
(The book Rust in Action guided me to visit here. It said here's the fantastic community always welcomes newbies.)
Please don't get me wrong since this is my last resort after struggling in several hours for googling any solutions.


I'm tring to use Tauri to build a web-based GUI application.
The problem is the existing backend module is a big python module which is about 3,000 lines of code.
After struggling in several hours, I happened to find this solution that works perfectly.
But I realized the module initialization with PyModule::from_code(py, PY_APP, "", "") might be time-consuming since it doesn't have any cache mechanism. (I checked it from the source code)

// Simlified example with `call0`
fn pychart() -> PyResult<String> {
    let py_foo = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/python_app/utils/foo.py"
    ));
    Python::with_gil(|py| {
        let module = PyModule::from_code(py, PY_APP, "", "")?; 
        let result = module.getattr("chart")?.call0()?;
        let value = result.extract()?;
        Ok(value)
    })
}

So, I finally started to find any way to share the initialized PyModule object around any rust functions which need to call python function like this, (but it failed :joy: I know the code doesn't make sense )

use pyo3::{prelude::*, types::PyModule};
static PY_APP: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/python_app/main.py"));
static PY_GIL: Python = unsafe { Python::assume_gil_acquired() };
static MODULE: Result<&PyModule, PyErr> = PyModule::from_code(py, PY_APP, "", "")?;
...

Any helps would be really appreciated.

You just need to use the Py type to convert a GIL scoped reference to a longer lived value. Then you can acquire the GIL when you need to use the reference again.

Here's an example of doing that

use pyo3::{types::PyModule, Py, Python};

fn main() {
    // Initialize the module once
    let module = Python::with_gil(|py| {
        let module: Py<PyModule> = PyModule::from_code(
            py,
            "\
print(\"starting\");
def function():
    print(\"Hi\")
",
            "fake.py",
            "fake",
        )
        // Panic on any errors since this is an example
        .unwrap()
        // Convert the GIL scoped reference into a long lived reference.
        .into();

        module
    });

    // Pass a reference to the GIL independent python object reference to the function.
    // This allows us to avoid re-initializing the module over and over.
    call(&module);
    // Second call shouldn't print "starting" again, since the module is already initialized
    call(&module);
}

fn call(module: &Py<PyModule>) {
    Python::with_gil(|py| {
        // Convert the GIL-independent reference into a usable reference scoped to the GIL lock closure
        let module = module.as_ref(py);

        // Get the function and call it.
        module.getattr("function").unwrap().call0().unwrap();
    });
}

The output should be

starting
Hi
Hi

Hi semicoleon, your comment was really insightful.

I tried to fix my code like this.
But it seems that inner functions doesn't capture the variable that was defined in the upper scope.

Can you please help me again? :pray:
(The program's structure is copied from auto-generated code by Tauri.)


use pyo3::{prelude::*, types::PyModule};
static PY_APP: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/python_app/main.py"));

fn main() {
    pyo3::prepare_freethreaded_python();

    Python::with_gil(|py| {
        let module: &PyModule = PyModule::from_code(py, PY_APP, "", "").unwrap().into();

        #[tauri::command]
        fn chart(name: &str) -> String {
            let result = &module.getattr("chart")?.call1((name,))?;
            let value = result.extract().unwrap().int();
            value
        }

        tauri::Builder::default()
            .invoke_handler(tauri::generate_handler![chart])
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    });
}

You need to set up some managed state with tauri. Then you can store the module in the managed state

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.