From 821b28daff25562262bc7dd4716efbfadc4df00a Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Mon, 4 Apr 2022 19:09:27 +0200 Subject: [PATCH 1/4] Intern the __qualname__ identifier used by PyType::name. --- src/types/typeobject.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index dfc97901..7c1e1fda 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -37,7 +37,7 @@ impl PyType { /// Gets the name of the `PyType`. pub fn name(&self) -> PyResult<&str> { - self.getattr("__qualname__")?.extract() + self.getattr(intern!(self.py(), "__qualname__"))?.extract() } /// Checks whether `self` is a subclass of `other`. From 5434bbc3f6bfad84d7808b5da3ca12cf1c6d90c2 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Mon, 4 Apr 2022 19:09:58 +0200 Subject: [PATCH 2/4] Intern the __all__ and __name__ identifiers used by PyModule. --- src/types/module.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/types/module.rs b/src/types/module.rs index facb4524..2838734b 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -8,8 +8,7 @@ use crate::exceptions; use crate::ffi; use crate::pyclass::PyClass; use crate::type_object::PyTypeObject; -use crate::types::PyCFunction; -use crate::types::{PyAny, PyDict, PyList}; +use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString}; use crate::{AsPyPointer, IntoPy, PyObject, Python}; use std::ffi::{CStr, CString}; use std::str; @@ -168,12 +167,13 @@ impl PyModule { /// /// `__all__` declares the items that will be imported with `from my_module import *`. pub fn index(&self) -> PyResult<&PyList> { - match self.getattr("__all__") { + let __all__ = __all__(self.py()); + match self.getattr(__all__) { Ok(idx) => idx.downcast().map_err(PyErr::from), Err(err) => { if err.is_instance_of::(self.py()) { let l = PyList::empty(self.py()); - self.setattr("__all__", l).map_err(PyErr::from)?; + self.setattr(__all__, l).map_err(PyErr::from)?; Ok(l) } else { Err(err) @@ -202,7 +202,6 @@ impl PyModule { /// May fail if the module does not have a `__file__` attribute. #[cfg(not(all(windows, PyPy)))] pub fn filename(&self) -> PyResult<&str> { - use crate::types::PyString; unsafe { self.py() .from_owned_ptr_or_err::(ffi::PyModule_GetFilenameObject(self.as_ptr()))? @@ -304,7 +303,7 @@ impl PyModule { { let py = self.py(); let function = wrapper(py).convert(py)?; - let name = function.getattr(py, "__name__")?; + let name = function.getattr(py, __name__(py))?; let name = name.extract(py)?; self.add(name, function) } @@ -389,11 +388,19 @@ impl PyModule { /// [1]: crate::prelude::pyfunction /// [2]: crate::wrap_pyfunction pub fn add_function<'a>(&'a self, fun: &'a PyCFunction) -> PyResult<()> { - let name = fun.getattr("__name__")?.extract()?; + let name = fun.getattr(__name__(self.py()))?.extract()?; self.add(name, fun) } } +fn __all__(py: Python<'_>) -> &PyString { + intern!(py, "__all__") +} + +fn __name__(py: Python<'_>) -> &PyString { + intern!(py, "__name__") +} + #[cfg(test)] mod tests { use crate::{types::PyModule, Python}; From 2c95b3abb4189226dc3d19f231662e1a599c3d3c Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Mon, 4 Apr 2022 19:10:28 +0200 Subject: [PATCH 3/4] Intern the attribute names used by the derive macro for FromPyObject. --- pyo3-macros-backend/src/frompyobject.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index c55f14b3..2af52079 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -294,8 +294,10 @@ impl<'a> Container<'a> { let mut fields: Punctuated = Punctuated::new(); for (ident, attrs) in tups { let getter = match &attrs.getter { - FieldGetter::GetAttr(Some(name)) => quote!(getattr(#name)), - FieldGetter::GetAttr(None) => quote!(getattr(stringify!(#ident))), + FieldGetter::GetAttr(Some(name)) => quote!(getattr(_pyo3::intern!(py, #name))), + FieldGetter::GetAttr(None) => { + quote!(getattr(_pyo3::intern!(py, stringify!(#ident)))) + } FieldGetter::GetItem(Some(key)) => quote!(get_item(#key)), FieldGetter::GetItem(None) => quote!(get_item(stringify!(#ident))), }; @@ -303,13 +305,14 @@ impl<'a> Container<'a> { format!("failed to extract field {}.{}", quote!(#self_ty), ident); let get_field = quote!(obj.#getter?); let extractor = match &attrs.from_py_with { - None => quote!( - #get_field.extract().map_err(|inner| { + None => quote!({ let py = _pyo3::PyNativeType::py(obj); - let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg); - new_err.set_cause(py, ::std::option::Option::Some(inner)); - new_err - })?), + #get_field.extract().map_err(|inner| { + let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg); + new_err.set_cause(py, ::std::option::Option::Some(inner)); + new_err + })? + }), Some(FromPyWithAttribute { value: expr_path, .. }) => quote! ( From f02a060a3dfb8932432dbac4dc81b6c3cc7369f1 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Mon, 4 Apr 2022 22:34:35 +0200 Subject: [PATCH 4/4] Add a hint on using intern! to Py{,Any}::{set,get}attr. --- src/instance.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/types/any.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/instance.rs b/src/instance.rs index 0e704358..a8c0997b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -534,6 +534,25 @@ impl Py { /// Retrieves an attribute value. /// /// This is equivalent to the Python expression `self.attr_name`. + /// + /// If calling this method becomes performance-critical, the [`intern!`] macro can be used + /// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. + /// + /// # Example: `intern!`ing the attribute name + /// + /// ``` + /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPy, Py, Python, PyObject, PyResult}; + /// # + /// #[pyfunction] + /// fn version(sys: Py, py: Python<'_>) -> PyResult { + /// sys.getattr(py, intern!(py, "version")) + /// } + /// # + /// # Python::with_gil(|py| { + /// # let sys = py.import("sys").unwrap().into_py(py); + /// # version(sys, py).unwrap(); + /// # }); + /// ``` pub fn getattr(&self, py: Python<'_>, attr_name: N) -> PyResult where N: ToPyObject, @@ -546,6 +565,25 @@ impl Py { /// Sets an attribute value. /// /// This is equivalent to the Python expression `self.attr_name = value`. + /// + /// If calling this method becomes performance-critical, the [`intern!`] macro can be used + /// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. + /// + /// # Example: `intern!`ing the attribute name + /// + /// ``` + /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPy, PyObject, Python, PyResult}; + /// # + /// #[pyfunction] + /// fn set_answer(ob: PyObject, py: Python<'_>) -> PyResult<()> { + /// ob.setattr(py, intern!(py, "answer"), 42) + /// } + /// # + /// # Python::with_gil(|py| { + /// # let ob = PyModule::new(py, "empty").unwrap().into_py(py); + /// # set_answer(ob, py).unwrap(); + /// # }); + /// ``` pub fn setattr(&self, py: Python<'_>, attr_name: N, value: V) -> PyResult<()> where N: ToPyObject, diff --git a/src/types/any.rs b/src/types/any.rs index 4e13b3e9..9ba9b515 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -111,6 +111,25 @@ impl PyAny { /// Retrieves an attribute value. /// /// This is equivalent to the Python expression `self.attr_name`. + /// + /// If calling this method becomes performance-critical, the [`intern!`] macro can be used + /// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. + /// + /// # Example: `intern!`ing the attribute name + /// + /// ``` + /// # use pyo3::{intern, pyfunction, types::PyModule, PyAny, Python, PyResult}; + /// # + /// #[pyfunction] + /// fn version(sys: &PyModule) -> PyResult<&PyAny> { + /// sys.getattr(intern!(sys.py(), "version")) + /// } + /// # + /// # Python::with_gil(|py| { + /// # let sys = py.import("sys").unwrap(); + /// # version(sys).unwrap(); + /// # }); + /// ``` pub fn getattr(&self, attr_name: N) -> PyResult<&PyAny> where N: ToPyObject, @@ -124,6 +143,25 @@ impl PyAny { /// Sets an attribute value. /// /// This is equivalent to the Python expression `self.attr_name = value`. + /// + /// If calling this method becomes performance-critical, the [`intern!`] macro can be used + /// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. + /// + /// # Example: `intern!`ing the attribute name + /// + /// ``` + /// # use pyo3::{intern, pyfunction, types::PyModule, PyAny, Python, PyResult}; + /// # + /// #[pyfunction] + /// fn set_answer(ob: &PyAny) -> PyResult<()> { + /// ob.setattr(intern!(ob.py(), "answer"), 42) + /// } + /// # + /// # Python::with_gil(|py| { + /// # let ob = PyModule::new(py, "empty").unwrap(); + /// # set_answer(ob).unwrap(); + /// # }); + /// ``` pub fn setattr(&self, attr_name: N, value: V) -> PyResult<()> where N: ToBorrowedObject,