From a4629d76aab32ee26c0003589f0914077ce67f5c Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sun, 3 Apr 2022 12:27:34 +0200 Subject: [PATCH] Add intern! macro which can be used to amortize the cost of creating Python objects by storing them inside a GILOnceCell. --- CHANGELOG.md | 1 + src/lib.rs | 1 + src/once_cell.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db19eba9431..453363f21be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow dependent crates to access config values from `pyo3-build-config` via cargo link dep env vars. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Added methods on `InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Added FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250) +- Add `intern!` macro which can be used to amortize the cost of creating Python objects by storing them inside a `GILOnceCell`. [#2269](https://github.com/PyO3/pyo3/pull/2269) ### Changed diff --git a/src/lib.rs b/src/lib.rs index ce2a1e24905..6da166235c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -375,6 +375,7 @@ pub mod impl_; mod instance; pub mod marker; pub mod marshal; +#[macro_use] pub mod once_cell; pub mod panic; pub mod prelude; diff --git a/src/once_cell.rs b/src/once_cell.rs index 739864c5ab0..4e835ba4752 100644 --- a/src/once_cell.rs +++ b/src/once_cell.rs @@ -101,3 +101,46 @@ impl GILOnceCell { Ok(()) } } + +/// Converts `value` into a Python object and stores it in static storage. The same Python object +/// is returned on each invocation. +/// +/// Because it is stored in a static, this object's destructor will not run. +/// +/// # Example: Using `intern!` to avoid needlessly recreating the same object +/// +/// ``` +/// use pyo3::intern; +/// # use pyo3::{pyfunction, types::PyDict, PyResult, Python}; +/// +/// #[pyfunction] +/// fn create_dict(py: Python<'_>) -> PyResult<&PyDict> { +/// let dict = PyDict::new(py); +/// // 👇 A new `PyString` is created +/// // for every call of this function +/// dict.set_item("foo", 42)?; +/// Ok(dict) +/// } +/// +/// #[pyfunction] +/// fn create_dict_faster(py: Python<'_>) -> PyResult<&PyDict> { +/// let dict = PyDict::new(py); +/// // 👇 A `PyString` is created once and reused +/// // for the lifetime of the program. +/// dict.set_item(intern!(py, "foo"), 42)?; +/// Ok(dict) +/// } +/// ``` +#[macro_export] +macro_rules! intern { + ($py: expr, $value: expr) => {{ + static INTERNED: $crate::once_cell::GILOnceCell<$crate::PyObject> = + $crate::once_cell::GILOnceCell::new(); + + INTERNED + .get_or_init($py, || { + $crate::conversion::ToPyObject::to_object($value, $py) + }) + .as_ref($py) + }}; +}