optimize mapping conversion for dict

This commit is contained in:
David Hewitt 2023-02-15 08:00:05 +00:00
parent d0d944cab4
commit 40709db801
2 changed files with 18 additions and 20 deletions

View File

@ -0,0 +1 @@
Optimize `PyMapping` conversion for `dict` inputs.

View File

@ -3,12 +3,8 @@
use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::err::{PyDowncastError, PyErr, PyResult};
use crate::once_cell::GILOnceCell; use crate::once_cell::GILOnceCell;
use crate::type_object::PyTypeInfo; use crate::type_object::PyTypeInfo;
use crate::types::{PyAny, PySequence, PyType}; use crate::types::{PyAny, PyDict, PySequence, PyType};
use crate::{ use crate::{ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToPyObject};
ffi, AsPyPointer, IntoPy, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToPyObject,
};
static MAPPING_ABC: GILOnceCell<PyResult<Py<PyType>>> = GILOnceCell::new();
/// Represents a reference to a Python object supporting the mapping protocol. /// Represents a reference to a Python object supporting the mapping protocol.
#[repr(transparent)] #[repr(transparent)]
@ -119,17 +115,14 @@ impl PyMapping {
} }
} }
fn get_mapping_abc(py: Python<'_>) -> Result<&PyType, PyErr> { static MAPPING_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();
fn get_mapping_abc(py: Python<'_>) -> PyResult<&PyType> {
MAPPING_ABC MAPPING_ABC
.get_or_init(py, || { .get_or_try_init(py, || {
Ok(py py.import("collections.abc")?.getattr("Mapping")?.extract()
.import("collections.abc")?
.getattr("Mapping")?
.downcast::<PyType>()?
.into_py(py))
}) })
.as_ref() .map(|ty| ty.as_ref(py))
.map_or_else(|e| Err(e.clone_ref(py)), |t| Ok(t.as_ref(py)))
} }
impl<'v> PyTryFrom<'v> for PyMapping { impl<'v> PyTryFrom<'v> for PyMapping {
@ -139,11 +132,15 @@ impl<'v> PyTryFrom<'v> for PyMapping {
fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> { fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
let value = value.into(); let value = value.into();
// TODO: surface specific errors in this chain to the user // Using `is_instance` for `collections.abc.Mapping` is slow, so provide
if let Ok(abc) = get_mapping_abc(value.py()) { // optimized case dict as a well-known mapping
if value.is_instance(abc).unwrap_or(false) { if PyDict::is_type_of(value)
unsafe { return Ok(value.downcast_unchecked()) } || get_mapping_abc(value.py())
} .and_then(|abc| value.is_instance(abc))
// TODO: surface errors in this chain to the user
.unwrap_or(false)
{
unsafe { return Ok(value.downcast_unchecked()) }
} }
Err(PyDowncastError::new(value, "Mapping")) Err(PyDowncastError::new(value, "Mapping"))