From 785f18cffa913f30b694eb168e82c67d3eb08ba2 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Mon, 13 Jul 2020 14:20:46 +0100 Subject: [PATCH] Add `Python::with_gil` --- CHANGELOG.md | 1 + README.md | 12 +- guide/src/python_from_rust.md | 78 +++++---- guide/src/trait_bounds.md | 318 ++++++++++++++++------------------ src/buffer.rs | 3 +- src/python.rs | 25 +++ 6 files changed, 226 insertions(+), 211 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd89a9458e..fa0f7af52e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - Add FFI definitions `Py_FinalizeEx`, `PyOS_getsig`, `PyOS_setsig`. [#1021](https://github.com/PyO3/pyo3/pull/1021) +- Add `Python::with_gil` for executing a closure with the Python GIL. [#1037](https://github.com/PyO3/pyo3/pull/1037) ### Changed - Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` argument instead of `*mut`. [#1021](https://github.com/PyO3/pyo3/pull/1021) diff --git a/README.md b/README.md index ae4fbbc8eb7..d118e78f74d 100644 --- a/README.md +++ b/README.md @@ -108,12 +108,12 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; fn main() -> Result<(), ()> { - let gil = Python::acquire_gil(); - let py = gil.python(); - main_(py).map_err(|e| { - // We can't display Python exceptions via std::fmt::Display, - // so print the error here manually. - e.print_and_set_sys_last_vars(py); + Python::with_gil(|py| { + main_(py).map_err(|e| { + // We can't display Python exceptions via std::fmt::Display, + // so print the error here manually. + e.print_and_set_sys_last_vars(py); + }) }) } diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index 83591f35e95..9b65261c549 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -13,12 +13,12 @@ module available in your environment. use pyo3::prelude::*; fn main() -> PyResult<()> { - let gil = Python::acquire_gil(); - let py = gil.python(); - let builtins = PyModule::import(py, "builtins")?; - let total: i32 = builtins.call1("sum", (vec![1, 2, 3],))?.extract()?; - assert_eq!(total, 6); - Ok(()) + Python::with_gil(|py| { + let builtins = PyModule::import(py, "builtins")?; + let total: i32 = builtins.call1("sum", (vec![1, 2, 3],))?.extract()?; + assert_eq!(total, 6); + Ok(()) + }) } ``` @@ -33,14 +33,14 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; fn main() -> Result<(), ()> { - let gil = Python::acquire_gil(); - let py = gil.python(); - let result = py.eval("[i * 10 for i in range(5)]", None, None).map_err(|e| { - e.print_and_set_sys_last_vars(py); - })?; - let res: Vec = result.extract().unwrap(); - assert_eq!(res, vec![0, 10, 20, 30, 40]); - Ok(()) + 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); + })?; + let res: Vec = result.extract().unwrap(); + assert_eq!(res, vec![0, 10, 20, 30, 40]); + Ok(()) + }) } ``` @@ -76,18 +76,19 @@ impl PyObjectProtocol for UserData { Ok(format!("User {}(id: {})", self.name, self.id)) } } -let gil = Python::acquire_gil(); -let py = gil.python(); -let userdata = UserData { - id: 34, - name: "Yu".to_string(), -}; -let userdata = PyCell::new(py, userdata).unwrap(); -let userdata_as_tuple = (34, "Yu"); -py_run!(py, userdata userdata_as_tuple, r#" + +Python::with_gil(|py| { + let userdata = UserData { + id: 34, + name: "Yu".to_string(), + }; + let userdata = PyCell::new(py, userdata).unwrap(); + let userdata_as_tuple = (34, "Yu"); + py_run!(py, userdata userdata_as_tuple, r#" assert repr(userdata) == "User Yu(id: 34)" assert userdata.as_tuple() == userdata_as_tuple -"#); + "#); +}) # } ``` @@ -100,26 +101,27 @@ can be used to generate a Python module which can then be used just as if it was ```rust use pyo3::{prelude::*, types::{IntoPyDict, PyModule}}; # fn main() -> PyResult<()> { -let gil = Python::acquire_gil(); -let py = gil.python(); -let activators = PyModule::from_code(py, r#" +Python::with_gil(|py| { + let activators = PyModule::from_code(py, r#" def relu(x): """see https://en.wikipedia.org/wiki/Rectifier_(neural_networks)""" return max(0.0, x) def leaky_relu(x, slope=0.01): return x if x >= 0 else x * slope -"#, "activators.py", "activators")?; - -let relu_result: f64 = activators.call1("relu", (-1.0,))?.extract()?; -assert_eq!(relu_result, 0.0); - -let kwargs = [("slope", 0.2)].into_py_dict(py); -let lrelu_result: f64 = activators - .call("leaky_relu", (-1.0,), Some(kwargs))? - .extract()?; -assert_eq!(lrelu_result, -0.2); -# Ok(()) } + "#, "activators.py", "activators")?; + + let relu_result: f64 = activators.call1("relu", (-1.0,))?.extract()?; + assert_eq!(relu_result, 0.0); + + let kwargs = [("slope", 0.2)].into_py_dict(py); + let lrelu_result: f64 = activators + .call("leaky_relu", (-1.0,), Some(kwargs))? + .extract()?; + assert_eq!(lrelu_result, -0.2); +# Ok(()) +}) +# } ``` [`Python::run`]: https://pyo3.rs/master/doc/pyo3/struct.Python.html#method.run diff --git a/guide/src/trait_bounds.md b/guide/src/trait_bounds.md index 6b4ef9d675e..e3aac085733 100644 --- a/guide/src/trait_bounds.md +++ b/guide/src/trait_bounds.md @@ -79,37 +79,36 @@ struct UserModel { impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { println!("Rust calling Python to set the variables"); - let gil = Python::acquire_gil(); - let py = gil.python(); - let values: Vec = var.clone(); - let list: PyObject = values.into_py(py); - let py_model = self.model.as_ref(py); - py_model - .call_method("set_variables", (list,), None) - .unwrap(); + Python::with_gil(|py| { + let values: Vec = var.clone(); + let list: PyObject = values.into_py(py); + let py_model = self.model.as_ref(py); + py_model + .call_method("set_variables", (list,), None) + .unwrap(); + }) } fn get_results(&self) -> Vec { println!("Rust calling Python to get the results"); - let gil = Python::acquire_gil(); - let py = gil.python(); - self - .model - .as_ref(py) - .call_method("get_results", (), None) - .unwrap() - .extract() - .unwrap() + Python::with_gil(|py| { + self.model + .as_ref(py) + .call_method("get_results", (), None) + .unwrap() + .extract() + .unwrap() + }) } fn compute(&mut self) { println!("Rust calling Python to perform the computation"); - let gil = Python::acquire_gil(); - let py = gil.python(); - self.model - .as_ref(py) - .call_method("compute", (), None) - .unwrap(); + Python::with_gil(|py| { + self.model + .as_ref(py) + .call_method("compute", (), None) + .unwrap(); + }) } } ``` @@ -180,61 +179,55 @@ This wrapper will also perform the type conversions between Python and Rust. # impl Model for UserModel { # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) -# .unwrap(); +# Python::with_gil(|py| { +# let values: Vec = var.clone(); +# let list: PyObject = values.into_py(py); +# let py_model = self.model.as_ref(py); +# py_model +# .call_method("set_variables", (list,), None) +# .unwrap(); +# }) # } # # fn get_results(&self) -> Vec { # println!("Rust calling Python to get the results"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# self -# .model -# .as_ref(py) -# .call_method("get_results", (), None) -# .unwrap() -# .extract() -# .unwrap() +# Python::with_gil(|py| { +# self.model +# .as_ref(py) +# .call_method("get_results", (), None) +# .unwrap() +# .extract() +# .unwrap() +# }) # } # # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# self.model -# .as_ref(py) -# .call_method("compute", (), None) -# .unwrap(); +# Python::with_gil(|py| { +# self.model +# .as_ref(py) +# .call_method("compute", (), None) +# .unwrap(); +# }) +# # } # } #[pymethods] impl UserModel { - pub fn set_variables(&mut self, var: Vec) -> PyResult<()> { + pub fn set_variables(&mut self, var: Vec) { println!("Set variables from Python calling Rust"); - Model::set_variables(self, &var); - Ok(()) + Model::set_variables(self, &var) } - pub fn get_results(&mut self) -> PyResult> { + pub fn get_results(&mut self) -> Vec { println!("Get results from Python calling Rust"); - let results = Model::get_results(self); - let gil = Python::acquire_gil(); - let py = gil.python(); - let py_results = results.into_py(py); - Ok(py_results) + Model::get_results(self) } - pub fn compute(&mut self) -> PyResult<()> { + pub fn compute(&mut self) { println!("Compute from Python calling Rust"); - Model::compute(self); - Ok(()) + Model::compute(self) } } ``` @@ -353,39 +346,38 @@ We used in our `get_results` method the following call that performs the type co # } impl Model for UserModel { - fn get_results(&self) -> Vec { - println!("Get results from Rust calling Python"); - let gil = Python::acquire_gil(); - let py = gil.python(); - self - .model - .as_ref(py) - .call_method("get_results", (), None) - .unwrap() - .extract() - .unwrap() + fn get_results(&self) -> Vec { + println!("Rust calling Python to get the results"); + Python::with_gil(|py| { + self.model + .as_ref(py) + .call_method("get_results", (), None) + .unwrap() + .extract() + .unwrap() + }) } -# fn set_variables(&mut self, var: &Vec) { -# println!("Rust calling Python to set the variables"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) -# .unwrap(); -# } +# fn set_variables(&mut self, var: &Vec) { +# println!("Rust calling Python to set the variables"); +# Python::with_gil(|py| { +# let values: Vec = var.clone(); +# let list: PyObject = values.into_py(py); +# let py_model = self.model.as_ref(py); +# py_model +# .call_method("set_variables", (list,), None) +# .unwrap(); +# }) +# } # -# fn compute(&mut self) { -# println!("Rust calling Python to perform the computation"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# self.model -# .as_ref(py) -# .call_method("compute", (), None) -# .unwrap(); -# } +# fn compute(&mut self) { +# println!("Rust calling Python to perform the computation"); +# Python::with_gil(|py| { +# self.model +# .as_ref(py) +# .call_method("compute", (), None) +# .unwrap(); +# }) +# } } ``` @@ -407,42 +399,43 @@ Let's break it down in order to perform better error handling: # } impl Model for UserModel { - fn get_results(&self) -> Vec { - println!("Get results from Rust calling Python"); - let gil = Python::acquire_gil(); - let py = gil.python(); - let py_result: &PyAny = self - .model - .as_ref(py) - .call_method("get_results", (), None) - .unwrap(); - - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); - } - py_result.extract().unwrap() - } -# fn set_variables(&mut self, var: &Vec) { -# println!("Rust calling Python to set the variables"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) -# .unwrap(); -# } + fn get_results(&self) -> Vec { + println!("Get results from Rust calling Python"); + Python::with_gil(|py| { + let py_result: &PyAny = self + .model + .as_ref(py) + .call_method("get_results", (), None) + .unwrap(); + + if py_result.get_type().name() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + } + py_result.extract() + }) + .unwrap() + } +# fn set_variables(&mut self, var: &Vec) { +# println!("Rust calling Python to set the variables"); +# Python::with_gil(|py| { +# let values: Vec = var.clone(); +# let list: PyObject = values.into_py(py); +# let py_model = self.model.as_ref(py); +# py_model +# .call_method("set_variables", (list,), None) +# .unwrap(); +# }) +# } # -# fn compute(&mut self) { -# println!("Rust calling Python to perform the computation"); -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# self.model -# .as_ref(py) -# .call_method("compute", (), None) -# .unwrap(); -# } +# fn compute(&mut self) { +# println!("Rust calling Python to perform the computation"); +# Python::with_gil(|py| { +# self.model +# .as_ref(py) +# .call_method("compute", (), None) +# .unwrap(); +# }) +# } } ``` @@ -495,7 +488,7 @@ pub struct UserModel { #[pymodule] fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; - m.add_wrapped(wrap_pyfunction!(solve_wrapper)).unwrap(); + m.add_wrapped(wrap_pyfunction!(solve_wrapper))?; Ok(()) } @@ -506,64 +499,59 @@ impl UserModel { UserModel { model } } - pub fn set_variables(&mut self, var: Vec) -> PyResult<()> { + pub fn set_variables(&mut self, var: Vec) { println!("Set variables from Python calling Rust"); - Model::set_variables(self, &var); - Ok(()) + Model::set_variables(self, &var) } - pub fn get_results(&mut self) -> PyResult> { + pub fn get_results(&mut self) -> Vec { println!("Get results from Python calling Rust"); - let results = Model::get_results(self); - let gil = Python::acquire_gil(); - let py = gil.python(); - let py_results = results.into_py(py); - Ok(py_results) + Model::get_results(self) } - pub fn compute(&mut self) -> PyResult<()> { - Model::compute(self); - Ok(()) + pub fn compute(&mut self) { + Model::compute(self) } } impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { - println!("Set variables from Rust calling Python"); - let gil = Python::acquire_gil(); - let py = gil.python(); - let values: Vec = var.clone(); - let list: PyObject = values.into_py(py); - let py_model = self.model.as_ref(py); - py_model - .call_method("set_variables", (list,), None) - .unwrap(); + println!("Rust calling Python to set the variables"); + Python::with_gil(|py| { + let values: Vec = var.clone(); + let list: PyObject = values.into_py(py); + let py_model = self.model.as_ref(py); + py_model + .call_method("set_variables", (list,), None) + .unwrap(); + }) } fn get_results(&self) -> Vec { println!("Get results from Rust calling Python"); - let gil = Python::acquire_gil(); - let py = gil.python(); - let py_result: &PyAny = self - .model - .as_ref(py) - .call_method("get_results", (), None) - .unwrap(); - - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); - } - py_result.extract().unwrap() + Python::with_gil(|py| { + let py_result: &PyAny = self + .model + .as_ref(py) + .call_method("get_results", (), None) + .unwrap(); + + if py_result.get_type().name() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + } + py_result.extract() + }) + .unwrap() } fn compute(&mut self) { - println!("Compute from Rust calling Python"); - let gil = Python::acquire_gil(); - let py = gil.python(); - self.model - .as_ref(py) - .call_method("compute", (), None) - .unwrap(); + println!("Rust calling Python to perform the computation"); + Python::with_gil(|py| { + self.model + .as_ref(py) + .call_method("compute", (), None) + .unwrap(); + }) } } ``` diff --git a/src/buffer.rs b/src/buffer.rs index fd004568b6b..3f0b3f128fe 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -569,8 +569,7 @@ fn buffer_readonly_error() -> PyResult<()> { impl Drop for PyBuffer { fn drop(&mut self) { - let _gil_guard = Python::acquire_gil(); - unsafe { ffi::PyBuffer_Release(&mut *self.0) } + Python::with_gil(|_| unsafe { ffi::PyBuffer_Release(&mut *self.0) }); } } diff --git a/src/python.rs b/src/python.rs index 081166dd62c..39137769a50 100644 --- a/src/python.rs +++ b/src/python.rs @@ -46,6 +46,31 @@ pub use gil::prepare_freethreaded_python; #[derive(Copy, Clone)] pub struct Python<'p>(PhantomData<&'p GILGuard>); +impl Python<'_> { + /// Acquires the global interpreter lock, which allows access to the Python runtime. The + /// provided closure F will be executed with the acquired `Python` marker token. + /// + /// If the Python runtime is not already initialized, this function will initialize it. + /// See [prepare_freethreaded_python()](fn.prepare_freethreaded_python.html) for details. + /// + /// # Example + /// ``` + /// use pyo3::prelude::*; + /// Python::with_gil(|py| -> PyResult<()> { + /// let x: i32 = py.eval("5", None, None)?.extract()?; + /// assert_eq!(x, 5); + /// Ok(()) + /// }); + /// ``` + #[inline] + pub fn with_gil(f: F) -> R + where + F: for<'p> FnOnce(Python<'p>) -> R, + { + f(unsafe { gil::ensure_gil().python() }) + } +} + impl<'p> Python<'p> { /// Retrieves a Python instance under the assumption that the GIL is already /// acquired at this point, and stays acquired for the lifetime `'p`.