Merge pull request #3660 from PyO3/type-name-fast-path

Rename name to qualname and add name to better match Python and ensure abi3 fallbacks.
This commit is contained in:
Adam Reichold 2023-12-19 16:36:44 +00:00 committed by GitHub
commit 87e42c96be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 90 additions and 14 deletions

View File

@ -232,7 +232,7 @@ impl Number {
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> { fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
// Get the class name dynamically in case `Number` is subclassed // Get the class name dynamically in case `Number` is subclassed
let class_name: &str = slf.get_type().name()?; let class_name: String = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0)) Ok(format!("{}({})", class_name, slf.borrow().0))
} }

View File

@ -87,7 +87,7 @@ the subclass name. This is typically done in Python code by accessing
impl Number { impl Number {
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> { fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
// This is the equivalent of `self.__class__.__name__` in Python. // This is the equivalent of `self.__class__.__name__` in Python.
let class_name: &str = slf.get_type().name()?; let class_name: String = slf.get_type().qualname()?;
// To access fields of the Rust struct, we need to borrow the `PyCell`. // To access fields of the Rust struct, we need to borrow the `PyCell`.
Ok(format!("{}({})", class_name, slf.borrow().0)) Ok(format!("{}({})", class_name, slf.borrow().0))
} }
@ -263,7 +263,7 @@ impl Number {
} }
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> { fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?; let class_name: String = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0)) Ok(format!("{}({})", class_name, slf.borrow().0))
} }

View File

@ -74,6 +74,9 @@ Python::with_gil(|py| {
}); });
``` ```
### `PyType::name` is now `PyType::qualname`
`PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes.
## from 0.19.* to 0.20 ## from 0.19.* to 0.20

View File

@ -0,0 +1 @@
`PyType::name` is now `PyType::qualname` whereas `PyType::name` efficiently accesses the full name which includes the module name.

View File

@ -1,4 +1,5 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use std::borrow::Cow;
#[pyfunction] #[pyfunction]
fn issue_219() { fn issue_219() {
@ -6,8 +7,14 @@ fn issue_219() {
Python::with_gil(|_| {}); Python::with_gil(|_| {});
} }
#[pyfunction]
fn get_type_full_name(obj: &PyAny) -> PyResult<Cow<'_, str>> {
obj.get_type().name()
}
#[pymodule] #[pymodule]
pub fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> { pub fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(issue_219, m)?)?; m.add_function(wrap_pyfunction!(issue_219, m)?)?;
m.add_function(wrap_pyfunction!(get_type_full_name, m)?)?;
Ok(()) Ok(())
} }

View File

@ -48,3 +48,9 @@ def test_import_in_subinterpreter_forbidden():
) )
_xxsubinterpreters.destroy(sub_interpreter) _xxsubinterpreters.destroy(sub_interpreter)
def test_type_full_name_includes_module():
numpy = pytest.importorskip("numpy")
assert pyo3_pytests.misc.get_type_full_name(numpy.bool_(True)) == "numpy.bool_"

View File

@ -706,7 +706,7 @@ impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Python::with_gil(|py| { Python::with_gil(|py| {
let value = self.value(py); let value = self.value(py);
let type_name = value.get_type().name().map_err(|_| std::fmt::Error)?; let type_name = value.get_type().qualname().map_err(|_| std::fmt::Error)?;
write!(f, "{}", type_name)?; write!(f, "{}", type_name)?;
if let Ok(s) = value.str() { if let Ok(s) = value.str() {
write!(f, ": {}", &s.to_string_lossy()) write!(f, ": {}", &s.to_string_lossy())
@ -748,7 +748,8 @@ impl PyErrArguments for PyDowncastErrorArguments {
"'{}' object cannot be converted to '{}'", "'{}' object cannot be converted to '{}'",
self.from self.from
.as_ref(py) .as_ref(py)
.name() .qualname()
.as_deref()
.unwrap_or("<failed to extract type name>"), .unwrap_or("<failed to extract type name>"),
self.to self.to
) )
@ -775,7 +776,10 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> {
write!( write!(
f, f,
"'{}' object cannot be converted to '{}'", "'{}' object cannot be converted to '{}'",
self.from.get_type().name().map_err(|_| std::fmt::Error)?, self.from
.get_type()
.qualname()
.map_err(|_| std::fmt::Error)?,
self.to self.to
) )
} }

View File

@ -1,5 +1,8 @@
use crate::err::{self, PyResult}; use crate::err::{self, PyResult};
use crate::{ffi, PyAny, PyTypeInfo, Python}; use crate::{ffi, PyAny, PyTypeInfo, Python};
use std::borrow::Cow;
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
use std::ffi::CStr;
/// Represents a reference to a Python `type object`. /// Represents a reference to a Python `type object`.
#[repr(transparent)] #[repr(transparent)]
@ -30,9 +33,58 @@ impl PyType {
py.from_borrowed_ptr(p as *mut ffi::PyObject) py.from_borrowed_ptr(p as *mut ffi::PyObject)
} }
/// Gets the name of the `PyType`. /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`.
pub fn name(&self) -> PyResult<&str> { pub fn qualname(&self) -> PyResult<String> {
self.getattr(intern!(self.py(), "__qualname__"))?.extract() #[cfg(any(Py_LIMITED_API, PyPy, not(Py_3_11)))]
let name = self.getattr(intern!(self.py(), "__qualname__"))?.extract();
#[cfg(not(any(Py_LIMITED_API, PyPy, not(Py_3_11))))]
let name = {
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
let obj = unsafe {
ffi::PyType_GetQualName(self.as_type_ptr()).assume_owned_or_err(self.py())?
};
obj.extract()
};
name
}
/// Gets the full name, which includes the module, of the `PyType`.
pub fn name(&self) -> PyResult<Cow<'_, str>> {
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
{
let ptr = self.as_type_ptr();
let name = unsafe { CStr::from_ptr((*ptr).tp_name) }.to_str()?;
#[cfg(Py_3_10)]
if unsafe { ffi::PyType_HasFeature(ptr, ffi::Py_TPFLAGS_IMMUTABLETYPE) } != 0 {
return Ok(Cow::Borrowed(name));
}
Ok(Cow::Owned(name.to_owned()))
}
#[cfg(any(Py_LIMITED_API, PyPy))]
{
let module = self.getattr(intern!(self.py(), "__module__"))?;
#[cfg(not(Py_3_11))]
let name = self.getattr(intern!(self.py(), "__name__"))?;
#[cfg(Py_3_11)]
let name = {
use crate::ffi_ptr_ext::FfiPtrExt;
unsafe { ffi::PyType_GetName(self.as_type_ptr()).assume_owned_or_err(self.py())? }
};
Ok(Cow::Owned(format!("{}.{}", module, name)))
}
} }
/// Checks whether `self` is a subclass of `other`. /// Checks whether `self` is a subclass of `other`.

View File

@ -136,7 +136,10 @@ fn cancelled_coroutine() {
None, None,
) )
.unwrap_err(); .unwrap_err();
assert_eq!(err.value(gil).get_type().name().unwrap(), "CancelledError"); assert_eq!(
err.value(gil).get_type().qualname().unwrap(),
"CancelledError"
);
}) })
} }

View File

@ -74,14 +74,14 @@ impl ClassMethod {
#[classmethod] #[classmethod]
/// Test class method. /// Test class method.
fn method(cls: &PyType) -> PyResult<String> { fn method(cls: &PyType) -> PyResult<String> {
Ok(format!("{}.method()!", cls.name()?)) Ok(format!("{}.method()!", cls.qualname()?))
} }
#[classmethod] #[classmethod]
fn method_owned(cls: Py<PyType>) -> PyResult<String> { fn method_owned(cls: Py<PyType>) -> PyResult<String> {
Ok(format!( Ok(format!(
"{}.method_owned()!", "{}.method_owned()!",
Python::with_gil(|gil| cls.as_ref(gil).name().map(ToString::to_string))? Python::with_gil(|gil| cls.as_ref(gil).qualname())?
)) ))
} }
} }
@ -109,7 +109,7 @@ struct ClassMethodWithArgs {}
impl ClassMethodWithArgs { impl ClassMethodWithArgs {
#[classmethod] #[classmethod]
fn method(cls: &PyType, input: &PyString) -> PyResult<String> { fn method(cls: &PyType, input: &PyString) -> PyResult<String> {
Ok(format!("{}.method({})", cls.name()?, input)) Ok(format!("{}.method({})", cls.qualname()?, input))
} }
} }
@ -937,7 +937,7 @@ impl r#RawIdents {
fn test_raw_idents() { fn test_raw_idents() {
Python::with_gil(|py| { Python::with_gil(|py| {
let raw_idents_type = py.get_type::<r#RawIdents>(); let raw_idents_type = py.get_type::<r#RawIdents>();
assert_eq!(raw_idents_type.name().unwrap(), "RawIdents"); assert_eq!(raw_idents_type.qualname().unwrap(), "RawIdents");
py_run!( py_run!(
py, py,
raw_idents_type, raw_idents_type,