Merge pull request #1114 from mtreinish/hashbrown
Add optional support for conversion from Hashbrown types
This commit is contained in:
commit
16ef96904a
|
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Implement type information for conversion failures. [#1050](https://github.com/PyO3/pyo3/pull/1050)
|
||||
- Add `PyBytes::new_with` and `PyByteArray::new_with` for initialising Python-allocated bytes and bytearrays using a closure. [#1074](https://github.com/PyO3/pyo3/pull/1074)
|
||||
- Add `Py::as_ref` and `Py::into_ref`. [#1098](https://github.com/PyO3/pyo3/pull/1098)
|
||||
- Add optional implementations of `ToPyObject`, `IntoPy`, and `FromPyObject` for [hashbrown](https://crates.io/crates/hashbrown)'s `HashMap` and `HashSet` types. The `hashbrown` feature must be enabled for these implementations to be built. [#1114](https://github.com/PyO3/pyo3/pull/1114/)
|
||||
|
||||
### Changed
|
||||
- Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024)
|
||||
|
|
|
@ -25,6 +25,7 @@ num-complex = { version = "0.3", optional = true }
|
|||
paste = { version = "0.1.6", optional = true }
|
||||
pyo3cls = { path = "pyo3cls", version = "=0.11.1", optional = true }
|
||||
unindent = { version = "0.1.4", optional = true }
|
||||
hashbrown = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
|
|
|
@ -24,10 +24,10 @@ The table below contains the Python type and the corresponding function argument
|
|||
| `float` | `f32`, `f64` | `&PyFloat` |
|
||||
| `complex` | `num_complex::Complex`[^1] | `&PyComplex` |
|
||||
| `list[T]` | `Vec<T>` | `&PyList` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>` | `&PyDict` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^2] | `&PyDict` |
|
||||
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
|
||||
| `set[T]` | `HashSet<T>`, `BTreeSet<T>` | `&PySet` |
|
||||
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>` | `&PyFrozenSet` |
|
||||
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PySet` |
|
||||
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PyFrozenSet` |
|
||||
| `bytearray` | `Vec<u8>` | `&PyByteArray` |
|
||||
| `slice` | - | `&PySlice` |
|
||||
| `type` | - | `&PyType` |
|
||||
|
@ -250,3 +250,4 @@ fn main() {
|
|||
[`PyRefMut`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRefMut.html
|
||||
|
||||
[^1]: Requires the `num-complex` optional feature.
|
||||
[^2]: Requires the `hashbrown` optional feature.
|
||||
|
|
|
@ -354,6 +354,97 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hashbrown")]
|
||||
mod hashbrown_hashmap_conversion {
|
||||
use super::*;
|
||||
use crate::{FromPyObject, PyErr, PyObject, ToPyObject};
|
||||
|
||||
impl<K, V, H> ToPyObject for hashbrown::HashMap<K, V, H>
|
||||
where
|
||||
K: hash::Hash + cmp::Eq + ToPyObject,
|
||||
V: ToPyObject,
|
||||
H: hash::BuildHasher,
|
||||
{
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
IntoPyDict::into_py_dict(self, py).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, H> IntoPy<PyObject> for hashbrown::HashMap<K, V, H>
|
||||
where
|
||||
K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
|
||||
V: IntoPy<PyObject>,
|
||||
H: hash::BuildHasher,
|
||||
{
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let iter = self
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.into_py(py), v.into_py(py)));
|
||||
IntoPyDict::into_py_dict(iter, py).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'source, K, V, S> FromPyObject<'source> for hashbrown::HashMap<K, V, S>
|
||||
where
|
||||
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
|
||||
V: FromPyObject<'source>,
|
||||
S: hash::BuildHasher + Default,
|
||||
{
|
||||
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
|
||||
let dict = <PyDict as PyTryFrom>::try_from(ob)?;
|
||||
let mut ret = hashbrown::HashMap::default();
|
||||
for (k, v) in dict.iter() {
|
||||
ret.insert(K::extract(k)?, V::extract(v)?);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hashbrown_hashmap_to_python() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let mut map = hashbrown::HashMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let m = map.to_object(py);
|
||||
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
|
||||
|
||||
assert!(py_map.len() == 1);
|
||||
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
|
||||
assert_eq!(map, py_map.extract().unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn test_hashbrown_hashmap_into_python() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let mut map = hashbrown::HashMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let m: PyObject = map.into_py(py);
|
||||
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
|
||||
|
||||
assert!(py_map.len() == 1);
|
||||
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hashbrown_hashmap_into_dict() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let mut map = hashbrown::HashMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let py_map = map.into_py_dict(py);
|
||||
|
||||
assert_eq!(py_map.len(), 1);
|
||||
assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::conversion::IntoPy;
|
||||
|
|
|
@ -300,6 +300,80 @@ impl<'a> std::iter::IntoIterator for &'a PyFrozenSet {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hashbrown")]
|
||||
mod hashbrown_hashset_conversion {
|
||||
use super::*;
|
||||
use crate::{FromPyObject, PyObject, PyResult, ToPyObject};
|
||||
|
||||
impl<T> ToPyObject for hashbrown::HashSet<T>
|
||||
where
|
||||
T: hash::Hash + Eq + ToPyObject,
|
||||
{
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
let set = PySet::new::<T>(py, &[]).expect("Failed to construct empty set");
|
||||
{
|
||||
for val in self {
|
||||
set.add(val).expect("Failed to add to set");
|
||||
}
|
||||
}
|
||||
set.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, S> IntoPy<PyObject> for hashbrown::HashSet<K, S>
|
||||
where
|
||||
K: IntoPy<PyObject> + Eq + hash::Hash,
|
||||
S: hash::BuildHasher + Default,
|
||||
{
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let set = PySet::empty(py).expect("Failed to construct empty set");
|
||||
{
|
||||
for val in self {
|
||||
set.add(val.into_py(py)).expect("Failed to add to set");
|
||||
}
|
||||
}
|
||||
set.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'source, K, S> FromPyObject<'source> for hashbrown::HashSet<K, S>
|
||||
where
|
||||
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
|
||||
S: hash::BuildHasher + Default,
|
||||
{
|
||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||
let set: &PySet = ob.downcast()?;
|
||||
set.iter().map(K::extract).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_hashbrown_hashset() {
|
||||
use std::iter::FromIterator;
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap();
|
||||
let hash_set: hashbrown::HashSet<usize> = set.extract().unwrap();
|
||||
assert_eq!(
|
||||
hash_set,
|
||||
hashbrown::HashSet::from_iter([1, 2, 3, 4, 5].iter().copied())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hashbrown_hashset_into_py() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let hs: hashbrown::HashSet<u64> = [1, 2, 3, 4, 5].iter().cloned().collect();
|
||||
|
||||
let hso: PyObject = hs.clone().into_py(py);
|
||||
|
||||
assert_eq!(hs, hso.extract(py).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{PyFrozenSet, PySet};
|
||||
|
|
Loading…
Reference in a new issue