From 5b1666290da5ffe8954884c48ce663c5186ea7b1 Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Tue, 10 Sep 2024 15:46:47 -0300 Subject: [PATCH] make `GILOnceCell::get_or_try_init_type_ref` public --- .../python-from-rust/calling-existing-code.md | 26 ++++++++++------ newsfragments/4542.changed.md | 1 + src/sync.rs | 30 ++++++++++++++++++- 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 newsfragments/4542.changed.md diff --git a/guide/src/python-from-rust/calling-existing-code.md b/guide/src/python-from-rust/calling-existing-code.md index 0e986562e19..3966b092a59 100644 --- a/guide/src/python-from-rust/calling-existing-code.md +++ b/guide/src/python-from-rust/calling-existing-code.md @@ -2,10 +2,9 @@ If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation: -## Want to access Python APIs? Then use `PyModule::import`. +## Want to access Python APIs? Then use `import`. -[`PyModule::import`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import) can -be used to get handle to a Python module from Rust. You can use this to import and use any Python +[`Python::import`] can be used to get handle to a Python module from Rust. You can use this to import and use any Python module available in your environment. ```rust @@ -13,7 +12,7 @@ use pyo3::prelude::*; fn main() -> PyResult<()> { Python::with_gil(|py| { - let builtins = PyModule::import(py, "builtins")?; + let builtins = py.import("builtins")?; let total: i32 = builtins .getattr("sum")? .call1((vec![1, 2, 3],))? @@ -24,9 +23,16 @@ fn main() -> PyResult<()> { } ``` -## Want to run just an expression? Then use `eval_bound`. +[`Python::import`] introduces an overhead each time it's called. To avoid this in functions that are called multiple times, +using a [`GILOnceCell`]({{#PYO3_DOCS_URL}}/pyo3/sync/struct.GILOnceCell.html) is recommended. Specifically, for importing types, +[`GILOnceCell::get_or_try_init_type_ref`]({{#PYO3_DOCS_URL}}/pyo3/sync/struct.GILOnceCell.html#method.get_or_try_init_type_ref) is provided +(check out the [example]({{#PYO3_DOCS_URL}}/pyo3/sync/struct.GILOnceCell.html#example-using-giloncecell-to-avoid-the-overhead-of-importing-a-class-multiple-times)). -[`Python::eval_bound`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval_bound) is +[`Python::import`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.import + +## Want to run just an expression? Then use `eval`. + +[`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is a method to execute a [Python expression](https://docs.python.org/3/reference/expressions.html) and return the evaluated value as a `Bound<'py, PyAny>` object. @@ -48,17 +54,19 @@ Python::with_gil(|py| { # } ``` -## Want to run statements? Then use `run_bound`. +## Want to run statements? Then use `run`. -[`Python::run_bound`] is a method to execute one or more +[`Python::run`] is a method to execute one or more [Python statements](https://docs.python.org/3/reference/simple_stmts.html). This method returns nothing (like any Python statement), but you can get access to manipulated objects via the `locals` dict. -You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run_bound`]. +You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run`]. Since [`py_run!`] panics on exceptions, we recommend you use this macro only for quickly testing your Python extensions. +[`Python::run`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.run + ```rust use pyo3::prelude::*; use pyo3::py_run; diff --git a/newsfragments/4542.changed.md b/newsfragments/4542.changed.md new file mode 100644 index 00000000000..3328d927b21 --- /dev/null +++ b/newsfragments/4542.changed.md @@ -0,0 +1 @@ +Change `GILOnceCell::get_or_try_init_type_ref` to public. diff --git a/src/sync.rs b/src/sync.rs index 59f669f2627..f28671e7d84 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -218,7 +218,35 @@ impl GILOnceCell> { /// Get a reference to the contained Python type, initializing it if needed. /// /// This is a shorthand method for `get_or_init` which imports the type from Python on init. - pub(crate) fn get_or_try_init_type_ref<'py>( + /// + /// # Example: Using `GILOnceCell` to avoid the overhead of importing a class multiple times + /// + /// ``` + /// # use pyo3::prelude::*; + /// # use pyo3::sync::GILOnceCell; + /// # use pyo3::types::{PyDict, PyType}; + /// # use pyo3::intern; + /// # + /// #[pyfunction] + /// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult> { + /// // Even if this function is called multiple times, + /// // the `OrderedDict` class will be imported only once. + /// static ORDERED_DICT: GILOnceCell> = GILOnceCell::new(); + /// ORDERED_DICT + /// .get_or_try_init_type_ref(py, "collections", "OrderedDict")? + /// .call1((dict,)) + /// } + /// + /// # Python::with_gil(|py| { + /// # let dict = PyDict::new(py); + /// # dict.set_item(intern!(py, "foo"), 42).unwrap(); + /// # let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap(); + /// # let ordered_dict = fun.call1((&dict,)).unwrap(); + /// # assert!(dict.eq(ordered_dict).unwrap()); + /// # }); + /// ``` + /// + pub fn get_or_try_init_type_ref<'py>( &self, py: Python<'py>, module_name: &str,