1use crate::err::{self, PyErr, PyResult};
2use crate::ffi::Py_ssize_t;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::instance::{Borrowed, Bound};
5use crate::py_result_ext::PyResultExt;
6use crate::types::{PyAny, PyAnyMethods, PyList, PyMapping};
7use crate::{ffi, BoundObject, IntoPyObject, IntoPyObjectExt, Python};
8
9#[repr(transparent)]
17pub struct PyDict(PyAny);
18
19pyobject_subclassable_native_type!(PyDict, crate::ffi::PyDictObject);
20
21pyobject_native_type!(
22 PyDict,
23 ffi::PyDictObject,
24 pyobject_native_static_type_object!(ffi::PyDict_Type),
25 #checkfunction=ffi::PyDict_Check
26);
27
28#[cfg(not(any(PyPy, GraalPy)))]
30#[repr(transparent)]
31pub struct PyDictKeys(PyAny);
32
33#[cfg(not(any(PyPy, GraalPy)))]
34pyobject_native_type_core!(
35 PyDictKeys,
36 pyobject_native_static_type_object!(ffi::PyDictKeys_Type),
37 #checkfunction=ffi::PyDictKeys_Check
38);
39
40#[cfg(not(any(PyPy, GraalPy)))]
42#[repr(transparent)]
43pub struct PyDictValues(PyAny);
44
45#[cfg(not(any(PyPy, GraalPy)))]
46pyobject_native_type_core!(
47 PyDictValues,
48 pyobject_native_static_type_object!(ffi::PyDictValues_Type),
49 #checkfunction=ffi::PyDictValues_Check
50);
51
52#[cfg(not(any(PyPy, GraalPy)))]
54#[repr(transparent)]
55pub struct PyDictItems(PyAny);
56
57#[cfg(not(any(PyPy, GraalPy)))]
58pyobject_native_type_core!(
59 PyDictItems,
60 pyobject_native_static_type_object!(ffi::PyDictItems_Type),
61 #checkfunction=ffi::PyDictItems_Check
62);
63
64impl PyDict {
65 pub fn new(py: Python<'_>) -> Bound<'_, PyDict> {
67 unsafe { ffi::PyDict_New().assume_owned(py).downcast_into_unchecked() }
68 }
69
70 #[deprecated(since = "0.23.0", note = "renamed to `PyDict::new`")]
72 #[inline]
73 pub fn new_bound(py: Python<'_>) -> Bound<'_, PyDict> {
74 Self::new(py)
75 }
76
77 #[cfg(not(any(PyPy, GraalPy)))]
85 pub fn from_sequence<'py>(seq: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyDict>> {
86 let py = seq.py();
87 let dict = Self::new(py);
88 err::error_on_minusone(py, unsafe {
89 ffi::PyDict_MergeFromSeq2(dict.as_ptr(), seq.as_ptr(), 1)
90 })?;
91 Ok(dict)
92 }
93
94 #[cfg(not(any(PyPy, GraalPy)))]
96 #[deprecated(since = "0.23.0", note = "renamed to `PyDict::from_sequence`")]
97 #[inline]
98 pub fn from_sequence_bound<'py>(seq: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyDict>> {
99 Self::from_sequence(seq)
100 }
101}
102
103#[doc(alias = "PyDict")]
109pub trait PyDictMethods<'py>: crate::sealed::Sealed {
110 fn copy(&self) -> PyResult<Bound<'py, PyDict>>;
114
115 fn clear(&self);
117
118 fn len(&self) -> usize;
122
123 fn is_empty(&self) -> bool;
125
126 fn contains<K>(&self, key: K) -> PyResult<bool>
130 where
131 K: IntoPyObject<'py>;
132
133 fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
139 where
140 K: IntoPyObject<'py>;
141
142 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
146 where
147 K: IntoPyObject<'py>,
148 V: IntoPyObject<'py>;
149
150 fn del_item<K>(&self, key: K) -> PyResult<()>
154 where
155 K: IntoPyObject<'py>;
156
157 fn keys(&self) -> Bound<'py, PyList>;
161
162 fn values(&self) -> Bound<'py, PyList>;
166
167 fn items(&self) -> Bound<'py, PyList>;
171
172 fn iter(&self) -> BoundDictIterator<'py>;
180
181 fn locked_for_each<F>(&self, closure: F) -> PyResult<()>
192 where
193 F: Fn(Bound<'py, PyAny>, Bound<'py, PyAny>) -> PyResult<()>;
194
195 fn as_mapping(&self) -> &Bound<'py, PyMapping>;
197
198 fn into_mapping(self) -> Bound<'py, PyMapping>;
200
201 fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
206
207 fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
216}
217
218impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> {
219 fn copy(&self) -> PyResult<Bound<'py, PyDict>> {
220 unsafe {
221 ffi::PyDict_Copy(self.as_ptr())
222 .assume_owned_or_err(self.py())
223 .downcast_into_unchecked()
224 }
225 }
226
227 fn clear(&self) {
228 unsafe { ffi::PyDict_Clear(self.as_ptr()) }
229 }
230
231 fn len(&self) -> usize {
232 dict_len(self) as usize
233 }
234
235 fn is_empty(&self) -> bool {
236 self.len() == 0
237 }
238
239 fn contains<K>(&self, key: K) -> PyResult<bool>
240 where
241 K: IntoPyObject<'py>,
242 {
243 fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult<bool> {
244 match unsafe { ffi::PyDict_Contains(dict.as_ptr(), key.as_ptr()) } {
245 1 => Ok(true),
246 0 => Ok(false),
247 _ => Err(PyErr::fetch(dict.py())),
248 }
249 }
250
251 let py = self.py();
252 inner(
253 self,
254 key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
255 )
256 }
257
258 fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
259 where
260 K: IntoPyObject<'py>,
261 {
262 fn inner<'py>(
263 dict: &Bound<'py, PyDict>,
264 key: Borrowed<'_, '_, PyAny>,
265 ) -> PyResult<Option<Bound<'py, PyAny>>> {
266 let py = dict.py();
267 let mut result: *mut ffi::PyObject = std::ptr::null_mut();
268 match unsafe {
269 ffi::compat::PyDict_GetItemRef(dict.as_ptr(), key.as_ptr(), &mut result)
270 } {
271 std::os::raw::c_int::MIN..=-1 => Err(PyErr::fetch(py)),
272 0 => Ok(None),
273 1..=std::os::raw::c_int::MAX => {
274 Ok(Some(unsafe { result.assume_owned_unchecked(py) }))
277 }
278 }
279 }
280
281 let py = self.py();
282 inner(
283 self,
284 key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
285 )
286 }
287
288 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
289 where
290 K: IntoPyObject<'py>,
291 V: IntoPyObject<'py>,
292 {
293 fn inner(
294 dict: &Bound<'_, PyDict>,
295 key: Borrowed<'_, '_, PyAny>,
296 value: Borrowed<'_, '_, PyAny>,
297 ) -> PyResult<()> {
298 err::error_on_minusone(dict.py(), unsafe {
299 ffi::PyDict_SetItem(dict.as_ptr(), key.as_ptr(), value.as_ptr())
300 })
301 }
302
303 let py = self.py();
304 inner(
305 self,
306 key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
307 value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
308 )
309 }
310
311 fn del_item<K>(&self, key: K) -> PyResult<()>
312 where
313 K: IntoPyObject<'py>,
314 {
315 fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult<()> {
316 err::error_on_minusone(dict.py(), unsafe {
317 ffi::PyDict_DelItem(dict.as_ptr(), key.as_ptr())
318 })
319 }
320
321 let py = self.py();
322 inner(
323 self,
324 key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
325 )
326 }
327
328 fn keys(&self) -> Bound<'py, PyList> {
329 unsafe {
330 ffi::PyDict_Keys(self.as_ptr())
331 .assume_owned(self.py())
332 .downcast_into_unchecked()
333 }
334 }
335
336 fn values(&self) -> Bound<'py, PyList> {
337 unsafe {
338 ffi::PyDict_Values(self.as_ptr())
339 .assume_owned(self.py())
340 .downcast_into_unchecked()
341 }
342 }
343
344 fn items(&self) -> Bound<'py, PyList> {
345 unsafe {
346 ffi::PyDict_Items(self.as_ptr())
347 .assume_owned(self.py())
348 .downcast_into_unchecked()
349 }
350 }
351
352 fn iter(&self) -> BoundDictIterator<'py> {
353 BoundDictIterator::new(self.clone())
354 }
355
356 fn locked_for_each<F>(&self, f: F) -> PyResult<()>
357 where
358 F: Fn(Bound<'py, PyAny>, Bound<'py, PyAny>) -> PyResult<()>,
359 {
360 #[cfg(feature = "nightly")]
361 {
362 self.iter().try_for_each(|(key, value)| f(key, value))
365 }
366
367 #[cfg(not(feature = "nightly"))]
368 {
369 crate::sync::with_critical_section(self, || {
370 self.iter().try_for_each(|(key, value)| f(key, value))
371 })
372 }
373 }
374
375 fn as_mapping(&self) -> &Bound<'py, PyMapping> {
376 unsafe { self.downcast_unchecked() }
377 }
378
379 fn into_mapping(self) -> Bound<'py, PyMapping> {
380 unsafe { self.into_any().downcast_into_unchecked() }
381 }
382
383 fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
384 err::error_on_minusone(self.py(), unsafe {
385 ffi::PyDict_Update(self.as_ptr(), other.as_ptr())
386 })
387 }
388
389 fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
390 err::error_on_minusone(self.py(), unsafe {
391 ffi::PyDict_Merge(self.as_ptr(), other.as_ptr(), 0)
392 })
393 }
394}
395
396impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
397 pub(crate) unsafe fn iter_borrowed(self) -> BorrowedDictIter<'a, 'py> {
403 BorrowedDictIter::new(self)
404 }
405}
406
407fn dict_len(dict: &Bound<'_, PyDict>) -> Py_ssize_t {
408 #[cfg(any(not(Py_3_8), PyPy, GraalPy, Py_LIMITED_API, Py_GIL_DISABLED))]
409 unsafe {
410 ffi::PyDict_Size(dict.as_ptr())
411 }
412
413 #[cfg(all(
414 Py_3_8,
415 not(PyPy),
416 not(GraalPy),
417 not(Py_LIMITED_API),
418 not(Py_GIL_DISABLED)
419 ))]
420 unsafe {
421 (*dict.as_ptr().cast::<ffi::PyDictObject>()).ma_used
422 }
423}
424
425pub struct BoundDictIterator<'py> {
427 dict: Bound<'py, PyDict>,
428 inner: DictIterImpl,
429}
430
431enum DictIterImpl {
432 DictIter {
433 ppos: ffi::Py_ssize_t,
434 di_used: ffi::Py_ssize_t,
435 remaining: ffi::Py_ssize_t,
436 },
437}
438
439impl DictIterImpl {
440 #[deny(unsafe_op_in_unsafe_fn)]
441 #[inline]
442 unsafe fn next_unchecked<'py>(
445 &mut self,
446 dict: &Bound<'py, PyDict>,
447 ) -> Option<(Bound<'py, PyAny>, Bound<'py, PyAny>)> {
448 match self {
449 Self::DictIter {
450 di_used,
451 remaining,
452 ppos,
453 ..
454 } => {
455 let ma_used = dict_len(dict);
456
457 if *di_used != ma_used {
462 *di_used = -1;
463 panic!("dictionary changed size during iteration");
464 };
465
466 if *remaining == -1 {
477 *di_used = -1;
478 panic!("dictionary keys changed during iteration");
479 };
480
481 let mut key: *mut ffi::PyObject = std::ptr::null_mut();
482 let mut value: *mut ffi::PyObject = std::ptr::null_mut();
483
484 if unsafe { ffi::PyDict_Next(dict.as_ptr(), ppos, &mut key, &mut value) != 0 } {
485 *remaining -= 1;
486 let py = dict.py();
487 Some((
491 unsafe { key.assume_borrowed_unchecked(py).to_owned() },
492 unsafe { value.assume_borrowed_unchecked(py).to_owned() },
493 ))
494 } else {
495 None
496 }
497 }
498 }
499 }
500
501 #[cfg(Py_GIL_DISABLED)]
502 #[inline]
503 fn with_critical_section<F, R>(&mut self, dict: &Bound<'_, PyDict>, f: F) -> R
504 where
505 F: FnOnce(&mut Self) -> R,
506 {
507 match self {
508 Self::DictIter { .. } => crate::sync::with_critical_section(dict, || f(self)),
509 }
510 }
511}
512
513impl<'py> Iterator for BoundDictIterator<'py> {
514 type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
515
516 #[inline]
517 fn next(&mut self) -> Option<Self::Item> {
518 #[cfg(Py_GIL_DISABLED)]
519 {
520 self.inner
521 .with_critical_section(&self.dict, |inner| unsafe {
522 inner.next_unchecked(&self.dict)
523 })
524 }
525 #[cfg(not(Py_GIL_DISABLED))]
526 {
527 unsafe { self.inner.next_unchecked(&self.dict) }
528 }
529 }
530
531 #[inline]
532 fn size_hint(&self) -> (usize, Option<usize>) {
533 let len = self.len();
534 (len, Some(len))
535 }
536
537 #[inline]
538 #[cfg(Py_GIL_DISABLED)]
539 fn fold<B, F>(mut self, init: B, mut f: F) -> B
540 where
541 Self: Sized,
542 F: FnMut(B, Self::Item) -> B,
543 {
544 self.inner.with_critical_section(&self.dict, |inner| {
545 let mut accum = init;
546 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
547 accum = f(accum, x);
548 }
549 accum
550 })
551 }
552
553 #[inline]
554 #[cfg(all(Py_GIL_DISABLED, feature = "nightly"))]
555 fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
556 where
557 Self: Sized,
558 F: FnMut(B, Self::Item) -> R,
559 R: std::ops::Try<Output = B>,
560 {
561 self.inner.with_critical_section(&self.dict, |inner| {
562 let mut accum = init;
563 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
564 accum = f(accum, x)?
565 }
566 R::from_output(accum)
567 })
568 }
569
570 #[inline]
571 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
572 fn all<F>(&mut self, mut f: F) -> bool
573 where
574 Self: Sized,
575 F: FnMut(Self::Item) -> bool,
576 {
577 self.inner.with_critical_section(&self.dict, |inner| {
578 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
579 if !f(x) {
580 return false;
581 }
582 }
583 true
584 })
585 }
586
587 #[inline]
588 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
589 fn any<F>(&mut self, mut f: F) -> bool
590 where
591 Self: Sized,
592 F: FnMut(Self::Item) -> bool,
593 {
594 self.inner.with_critical_section(&self.dict, |inner| {
595 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
596 if f(x) {
597 return true;
598 }
599 }
600 false
601 })
602 }
603
604 #[inline]
605 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
606 fn find<P>(&mut self, mut predicate: P) -> Option<Self::Item>
607 where
608 Self: Sized,
609 P: FnMut(&Self::Item) -> bool,
610 {
611 self.inner.with_critical_section(&self.dict, |inner| {
612 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
613 if predicate(&x) {
614 return Some(x);
615 }
616 }
617 None
618 })
619 }
620
621 #[inline]
622 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
623 fn find_map<B, F>(&mut self, mut f: F) -> Option<B>
624 where
625 Self: Sized,
626 F: FnMut(Self::Item) -> Option<B>,
627 {
628 self.inner.with_critical_section(&self.dict, |inner| {
629 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
630 if let found @ Some(_) = f(x) {
631 return found;
632 }
633 }
634 None
635 })
636 }
637
638 #[inline]
639 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
640 fn position<P>(&mut self, mut predicate: P) -> Option<usize>
641 where
642 Self: Sized,
643 P: FnMut(Self::Item) -> bool,
644 {
645 self.inner.with_critical_section(&self.dict, |inner| {
646 let mut acc = 0;
647 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
648 if predicate(x) {
649 return Some(acc);
650 }
651 acc += 1;
652 }
653 None
654 })
655 }
656}
657
658impl ExactSizeIterator for BoundDictIterator<'_> {
659 fn len(&self) -> usize {
660 match self.inner {
661 DictIterImpl::DictIter { remaining, .. } => remaining as usize,
662 }
663 }
664}
665
666impl<'py> BoundDictIterator<'py> {
667 fn new(dict: Bound<'py, PyDict>) -> Self {
668 let remaining = dict_len(&dict);
669
670 Self {
671 dict,
672 inner: DictIterImpl::DictIter {
673 ppos: 0,
674 di_used: remaining,
675 remaining,
676 },
677 }
678 }
679}
680
681impl<'py> IntoIterator for Bound<'py, PyDict> {
682 type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
683 type IntoIter = BoundDictIterator<'py>;
684
685 fn into_iter(self) -> Self::IntoIter {
686 BoundDictIterator::new(self)
687 }
688}
689
690impl<'py> IntoIterator for &Bound<'py, PyDict> {
691 type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
692 type IntoIter = BoundDictIterator<'py>;
693
694 fn into_iter(self) -> Self::IntoIter {
695 self.iter()
696 }
697}
698
699mod borrowed_iter {
700 use super::*;
701
702 pub struct BorrowedDictIter<'a, 'py> {
706 dict: Borrowed<'a, 'py, PyDict>,
707 ppos: ffi::Py_ssize_t,
708 len: ffi::Py_ssize_t,
709 }
710
711 impl<'a, 'py> Iterator for BorrowedDictIter<'a, 'py> {
712 type Item = (Borrowed<'a, 'py, PyAny>, Borrowed<'a, 'py, PyAny>);
713
714 #[inline]
715 fn next(&mut self) -> Option<Self::Item> {
716 let mut key: *mut ffi::PyObject = std::ptr::null_mut();
717 let mut value: *mut ffi::PyObject = std::ptr::null_mut();
718
719 if unsafe { ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) }
721 != 0
722 {
723 let py = self.dict.py();
724 self.len -= 1;
725 Some(unsafe { (key.assume_borrowed(py), value.assume_borrowed(py)) })
729 } else {
730 None
731 }
732 }
733
734 #[inline]
735 fn size_hint(&self) -> (usize, Option<usize>) {
736 let len = self.len();
737 (len, Some(len))
738 }
739 }
740
741 impl ExactSizeIterator for BorrowedDictIter<'_, '_> {
742 fn len(&self) -> usize {
743 self.len as usize
744 }
745 }
746
747 impl<'a, 'py> BorrowedDictIter<'a, 'py> {
748 pub(super) fn new(dict: Borrowed<'a, 'py, PyDict>) -> Self {
749 let len = dict_len(&dict);
750 BorrowedDictIter { dict, ppos: 0, len }
751 }
752 }
753}
754
755pub(crate) use borrowed_iter::BorrowedDictIter;
756
757pub trait IntoPyDict<'py>: Sized {
760 fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>>;
763
764 #[deprecated(since = "0.23.0", note = "renamed to `IntoPyDict::into_py_dict`")]
766 #[inline]
767 fn into_py_dict_bound(self, py: Python<'py>) -> Bound<'py, PyDict> {
768 self.into_py_dict(py).unwrap()
769 }
770}
771
772impl<'py, T, I> IntoPyDict<'py> for I
773where
774 T: PyDictItem<'py>,
775 I: IntoIterator<Item = T>,
776{
777 fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
778 let dict = PyDict::new(py);
779 self.into_iter().try_for_each(|item| {
780 let (key, value) = item.unpack();
781 dict.set_item(key, value)
782 })?;
783 Ok(dict)
784 }
785}
786
787trait PyDictItem<'py> {
789 type K: IntoPyObject<'py>;
790 type V: IntoPyObject<'py>;
791 fn unpack(self) -> (Self::K, Self::V);
792}
793
794impl<'py, K, V> PyDictItem<'py> for (K, V)
795where
796 K: IntoPyObject<'py>,
797 V: IntoPyObject<'py>,
798{
799 type K = K;
800 type V = V;
801
802 fn unpack(self) -> (Self::K, Self::V) {
803 (self.0, self.1)
804 }
805}
806
807impl<'a, 'py, K, V> PyDictItem<'py> for &'a (K, V)
808where
809 &'a K: IntoPyObject<'py>,
810 &'a V: IntoPyObject<'py>,
811{
812 type K = &'a K;
813 type V = &'a V;
814
815 fn unpack(self) -> (Self::K, Self::V) {
816 (&self.0, &self.1)
817 }
818}
819
820#[cfg(test)]
821mod tests {
822 use super::*;
823 use crate::types::PyTuple;
824 use std::collections::{BTreeMap, HashMap};
825
826 #[test]
827 fn test_new() {
828 Python::with_gil(|py| {
829 let dict = [(7, 32)].into_py_dict(py).unwrap();
830 assert_eq!(
831 32,
832 dict.get_item(7i32)
833 .unwrap()
834 .unwrap()
835 .extract::<i32>()
836 .unwrap()
837 );
838 assert!(dict.get_item(8i32).unwrap().is_none());
839 let map: HashMap<i32, i32> = [(7, 32)].iter().cloned().collect();
840 assert_eq!(map, dict.extract().unwrap());
841 let map: BTreeMap<i32, i32> = [(7, 32)].iter().cloned().collect();
842 assert_eq!(map, dict.extract().unwrap());
843 });
844 }
845
846 #[test]
847 #[cfg(not(any(PyPy, GraalPy)))]
848 fn test_from_sequence() {
849 Python::with_gil(|py| {
850 let items = PyList::new(py, vec![("a", 1), ("b", 2)]).unwrap();
851 let dict = PyDict::from_sequence(&items).unwrap();
852 assert_eq!(
853 1,
854 dict.get_item("a")
855 .unwrap()
856 .unwrap()
857 .extract::<i32>()
858 .unwrap()
859 );
860 assert_eq!(
861 2,
862 dict.get_item("b")
863 .unwrap()
864 .unwrap()
865 .extract::<i32>()
866 .unwrap()
867 );
868 let map: HashMap<String, i32> =
869 [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
870 assert_eq!(map, dict.extract().unwrap());
871 let map: BTreeMap<String, i32> =
872 [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
873 assert_eq!(map, dict.extract().unwrap());
874 });
875 }
876
877 #[test]
878 #[cfg(not(any(PyPy, GraalPy)))]
879 fn test_from_sequence_err() {
880 Python::with_gil(|py| {
881 let items = PyList::new(py, vec!["a", "b"]).unwrap();
882 assert!(PyDict::from_sequence(&items).is_err());
883 });
884 }
885
886 #[test]
887 fn test_copy() {
888 Python::with_gil(|py| {
889 let dict = [(7, 32)].into_py_dict(py).unwrap();
890
891 let ndict = dict.copy().unwrap();
892 assert_eq!(
893 32,
894 ndict
895 .get_item(7i32)
896 .unwrap()
897 .unwrap()
898 .extract::<i32>()
899 .unwrap()
900 );
901 assert!(ndict.get_item(8i32).unwrap().is_none());
902 });
903 }
904
905 #[test]
906 fn test_len() {
907 Python::with_gil(|py| {
908 let mut v = HashMap::<i32, i32>::new();
909 let dict = (&v).into_pyobject(py).unwrap();
910 assert_eq!(0, dict.len());
911 v.insert(7, 32);
912 let dict2 = v.into_pyobject(py).unwrap();
913 assert_eq!(1, dict2.len());
914 });
915 }
916
917 #[test]
918 fn test_contains() {
919 Python::with_gil(|py| {
920 let mut v = HashMap::new();
921 v.insert(7, 32);
922 let dict = v.into_pyobject(py).unwrap();
923 assert!(dict.contains(7i32).unwrap());
924 assert!(!dict.contains(8i32).unwrap());
925 });
926 }
927
928 #[test]
929 fn test_get_item() {
930 Python::with_gil(|py| {
931 let mut v = HashMap::new();
932 v.insert(7, 32);
933 let dict = v.into_pyobject(py).unwrap();
934 assert_eq!(
935 32,
936 dict.get_item(7i32)
937 .unwrap()
938 .unwrap()
939 .extract::<i32>()
940 .unwrap()
941 );
942 assert!(dict.get_item(8i32).unwrap().is_none());
943 });
944 }
945
946 #[cfg(feature = "macros")]
947 #[test]
948 fn test_get_item_error_path() {
949 use crate::exceptions::PyTypeError;
950
951 #[crate::pyclass(crate = "crate")]
952 struct HashErrors;
953
954 #[crate::pymethods(crate = "crate")]
955 impl HashErrors {
956 #[new]
957 fn new() -> Self {
958 HashErrors {}
959 }
960
961 fn __hash__(&self) -> PyResult<isize> {
962 Err(PyTypeError::new_err("Error from __hash__"))
963 }
964 }
965
966 Python::with_gil(|py| {
967 let class = py.get_type::<HashErrors>();
968 let instance = class.call0().unwrap();
969 let d = PyDict::new(py);
970 match d.get_item(instance) {
971 Ok(_) => {
972 panic!("this get_item call should always error")
973 }
974 Err(err) => {
975 assert!(err.is_instance_of::<PyTypeError>(py));
976 assert_eq!(err.value(py).to_string(), "Error from __hash__")
977 }
978 }
979 })
980 }
981
982 #[test]
983 fn test_set_item() {
984 Python::with_gil(|py| {
985 let mut v = HashMap::new();
986 v.insert(7, 32);
987 let dict = v.into_pyobject(py).unwrap();
988 assert!(dict.set_item(7i32, 42i32).is_ok()); assert!(dict.set_item(8i32, 123i32).is_ok()); assert_eq!(
991 42i32,
992 dict.get_item(7i32)
993 .unwrap()
994 .unwrap()
995 .extract::<i32>()
996 .unwrap()
997 );
998 assert_eq!(
999 123i32,
1000 dict.get_item(8i32)
1001 .unwrap()
1002 .unwrap()
1003 .extract::<i32>()
1004 .unwrap()
1005 );
1006 });
1007 }
1008
1009 #[test]
1010 fn test_set_item_refcnt() {
1011 Python::with_gil(|py| {
1012 let cnt;
1013 let obj = py.eval(ffi::c_str!("object()"), None, None).unwrap();
1014 {
1015 cnt = obj.get_refcnt();
1016 let _dict = [(10, &obj)].into_py_dict(py);
1017 }
1018 {
1019 assert_eq!(cnt, obj.get_refcnt());
1020 }
1021 });
1022 }
1023
1024 #[test]
1025 fn test_set_item_does_not_update_original_object() {
1026 Python::with_gil(|py| {
1027 let mut v = HashMap::new();
1028 v.insert(7, 32);
1029 let dict = (&v).into_pyobject(py).unwrap();
1030 assert!(dict.set_item(7i32, 42i32).is_ok()); assert!(dict.set_item(8i32, 123i32).is_ok()); assert_eq!(32i32, v[&7i32]); assert_eq!(None, v.get(&8i32));
1034 });
1035 }
1036
1037 #[test]
1038 fn test_del_item() {
1039 Python::with_gil(|py| {
1040 let mut v = HashMap::new();
1041 v.insert(7, 32);
1042 let dict = v.into_pyobject(py).unwrap();
1043 assert!(dict.del_item(7i32).is_ok());
1044 assert_eq!(0, dict.len());
1045 assert!(dict.get_item(7i32).unwrap().is_none());
1046 });
1047 }
1048
1049 #[test]
1050 fn test_del_item_does_not_update_original_object() {
1051 Python::with_gil(|py| {
1052 let mut v = HashMap::new();
1053 v.insert(7, 32);
1054 let dict = (&v).into_pyobject(py).unwrap();
1055 assert!(dict.del_item(7i32).is_ok()); assert_eq!(32i32, *v.get(&7i32).unwrap()); });
1058 }
1059
1060 #[test]
1061 fn test_items() {
1062 Python::with_gil(|py| {
1063 let mut v = HashMap::new();
1064 v.insert(7, 32);
1065 v.insert(8, 42);
1066 v.insert(9, 123);
1067 let dict = v.into_pyobject(py).unwrap();
1068 let mut key_sum = 0;
1070 let mut value_sum = 0;
1071 for el in dict.items() {
1072 let tuple = el.downcast::<PyTuple>().unwrap();
1073 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
1074 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
1075 }
1076 assert_eq!(7 + 8 + 9, key_sum);
1077 assert_eq!(32 + 42 + 123, value_sum);
1078 });
1079 }
1080
1081 #[test]
1082 fn test_keys() {
1083 Python::with_gil(|py| {
1084 let mut v = HashMap::new();
1085 v.insert(7, 32);
1086 v.insert(8, 42);
1087 v.insert(9, 123);
1088 let dict = v.into_pyobject(py).unwrap();
1089 let mut key_sum = 0;
1091 for el in dict.keys() {
1092 key_sum += el.extract::<i32>().unwrap();
1093 }
1094 assert_eq!(7 + 8 + 9, key_sum);
1095 });
1096 }
1097
1098 #[test]
1099 fn test_values() {
1100 Python::with_gil(|py| {
1101 let mut v = HashMap::new();
1102 v.insert(7, 32);
1103 v.insert(8, 42);
1104 v.insert(9, 123);
1105 let dict = v.into_pyobject(py).unwrap();
1106 let mut values_sum = 0;
1108 for el in dict.values() {
1109 values_sum += el.extract::<i32>().unwrap();
1110 }
1111 assert_eq!(32 + 42 + 123, values_sum);
1112 });
1113 }
1114
1115 #[test]
1116 fn test_iter() {
1117 Python::with_gil(|py| {
1118 let mut v = HashMap::new();
1119 v.insert(7, 32);
1120 v.insert(8, 42);
1121 v.insert(9, 123);
1122 let dict = v.into_pyobject(py).unwrap();
1123 let mut key_sum = 0;
1124 let mut value_sum = 0;
1125 for (key, value) in dict {
1126 key_sum += key.extract::<i32>().unwrap();
1127 value_sum += value.extract::<i32>().unwrap();
1128 }
1129 assert_eq!(7 + 8 + 9, key_sum);
1130 assert_eq!(32 + 42 + 123, value_sum);
1131 });
1132 }
1133
1134 #[test]
1135 fn test_iter_bound() {
1136 Python::with_gil(|py| {
1137 let mut v = HashMap::new();
1138 v.insert(7, 32);
1139 v.insert(8, 42);
1140 v.insert(9, 123);
1141 let dict = v.into_pyobject(py).unwrap();
1142 let mut key_sum = 0;
1143 let mut value_sum = 0;
1144 for (key, value) in dict {
1145 key_sum += key.extract::<i32>().unwrap();
1146 value_sum += value.extract::<i32>().unwrap();
1147 }
1148 assert_eq!(7 + 8 + 9, key_sum);
1149 assert_eq!(32 + 42 + 123, value_sum);
1150 });
1151 }
1152
1153 #[test]
1154 fn test_iter_value_mutated() {
1155 Python::with_gil(|py| {
1156 let mut v = HashMap::new();
1157 v.insert(7, 32);
1158 v.insert(8, 42);
1159 v.insert(9, 123);
1160
1161 let dict = (&v).into_pyobject(py).unwrap();
1162
1163 for (key, value) in &dict {
1164 dict.set_item(key, value.extract::<i32>().unwrap() + 7)
1165 .unwrap();
1166 }
1167 });
1168 }
1169
1170 #[test]
1171 #[should_panic]
1172 fn test_iter_key_mutated() {
1173 Python::with_gil(|py| {
1174 let mut v = HashMap::new();
1175 for i in 0..10 {
1176 v.insert(i * 2, i * 2);
1177 }
1178 let dict = v.into_pyobject(py).unwrap();
1179
1180 for (i, (key, value)) in dict.iter().enumerate() {
1181 let key = key.extract::<i32>().unwrap();
1182 let value = value.extract::<i32>().unwrap();
1183
1184 dict.set_item(key + 1, value + 1).unwrap();
1185
1186 if i > 1000 {
1187 break;
1189 };
1190 }
1191 });
1192 }
1193
1194 #[test]
1195 #[should_panic]
1196 fn test_iter_key_mutated_constant_len() {
1197 Python::with_gil(|py| {
1198 let mut v = HashMap::new();
1199 for i in 0..10 {
1200 v.insert(i * 2, i * 2);
1201 }
1202 let dict = v.into_pyobject(py).unwrap();
1203
1204 for (i, (key, value)) in dict.iter().enumerate() {
1205 let key = key.extract::<i32>().unwrap();
1206 let value = value.extract::<i32>().unwrap();
1207 dict.del_item(key).unwrap();
1208 dict.set_item(key + 1, value + 1).unwrap();
1209
1210 if i > 1000 {
1211 break;
1213 };
1214 }
1215 });
1216 }
1217
1218 #[test]
1219 fn test_iter_size_hint() {
1220 Python::with_gil(|py| {
1221 let mut v = HashMap::new();
1222 v.insert(7, 32);
1223 v.insert(8, 42);
1224 v.insert(9, 123);
1225 let dict = (&v).into_pyobject(py).unwrap();
1226
1227 let mut iter = dict.iter();
1228 assert_eq!(iter.size_hint(), (v.len(), Some(v.len())));
1229 iter.next();
1230 assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1)));
1231
1232 for _ in &mut iter {}
1234
1235 assert_eq!(iter.size_hint(), (0, Some(0)));
1236
1237 assert!(iter.next().is_none());
1238
1239 assert_eq!(iter.size_hint(), (0, Some(0)));
1240 });
1241 }
1242
1243 #[test]
1244 fn test_into_iter() {
1245 Python::with_gil(|py| {
1246 let mut v = HashMap::new();
1247 v.insert(7, 32);
1248 v.insert(8, 42);
1249 v.insert(9, 123);
1250 let dict = v.into_pyobject(py).unwrap();
1251 let mut key_sum = 0;
1252 let mut value_sum = 0;
1253 for (key, value) in dict {
1254 key_sum += key.extract::<i32>().unwrap();
1255 value_sum += value.extract::<i32>().unwrap();
1256 }
1257 assert_eq!(7 + 8 + 9, key_sum);
1258 assert_eq!(32 + 42 + 123, value_sum);
1259 });
1260 }
1261
1262 #[test]
1263 fn test_hashmap_into_dict() {
1264 Python::with_gil(|py| {
1265 let mut map = HashMap::<i32, i32>::new();
1266 map.insert(1, 1);
1267
1268 let py_map = map.into_py_dict(py).unwrap();
1269
1270 assert_eq!(py_map.len(), 1);
1271 assert_eq!(
1272 py_map
1273 .get_item(1)
1274 .unwrap()
1275 .unwrap()
1276 .extract::<i32>()
1277 .unwrap(),
1278 1
1279 );
1280 });
1281 }
1282
1283 #[test]
1284 fn test_btreemap_into_dict() {
1285 Python::with_gil(|py| {
1286 let mut map = BTreeMap::<i32, i32>::new();
1287 map.insert(1, 1);
1288
1289 let py_map = map.into_py_dict(py).unwrap();
1290
1291 assert_eq!(py_map.len(), 1);
1292 assert_eq!(
1293 py_map
1294 .get_item(1)
1295 .unwrap()
1296 .unwrap()
1297 .extract::<i32>()
1298 .unwrap(),
1299 1
1300 );
1301 });
1302 }
1303
1304 #[test]
1305 fn test_vec_into_dict() {
1306 Python::with_gil(|py| {
1307 let vec = vec![("a", 1), ("b", 2), ("c", 3)];
1308 let py_map = vec.into_py_dict(py).unwrap();
1309
1310 assert_eq!(py_map.len(), 3);
1311 assert_eq!(
1312 py_map
1313 .get_item("b")
1314 .unwrap()
1315 .unwrap()
1316 .extract::<i32>()
1317 .unwrap(),
1318 2
1319 );
1320 });
1321 }
1322
1323 #[test]
1324 fn test_slice_into_dict() {
1325 Python::with_gil(|py| {
1326 let arr = [("a", 1), ("b", 2), ("c", 3)];
1327 let py_map = arr.into_py_dict(py).unwrap();
1328
1329 assert_eq!(py_map.len(), 3);
1330 assert_eq!(
1331 py_map
1332 .get_item("b")
1333 .unwrap()
1334 .unwrap()
1335 .extract::<i32>()
1336 .unwrap(),
1337 2
1338 );
1339 });
1340 }
1341
1342 #[test]
1343 fn dict_as_mapping() {
1344 Python::with_gil(|py| {
1345 let mut map = HashMap::<i32, i32>::new();
1346 map.insert(1, 1);
1347
1348 let py_map = map.into_py_dict(py).unwrap();
1349
1350 assert_eq!(py_map.as_mapping().len().unwrap(), 1);
1351 assert_eq!(
1352 py_map
1353 .as_mapping()
1354 .get_item(1)
1355 .unwrap()
1356 .extract::<i32>()
1357 .unwrap(),
1358 1
1359 );
1360 });
1361 }
1362
1363 #[test]
1364 fn dict_into_mapping() {
1365 Python::with_gil(|py| {
1366 let mut map = HashMap::<i32, i32>::new();
1367 map.insert(1, 1);
1368
1369 let py_map = map.into_py_dict(py).unwrap();
1370
1371 let py_mapping = py_map.into_mapping();
1372 assert_eq!(py_mapping.len().unwrap(), 1);
1373 assert_eq!(py_mapping.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
1374 });
1375 }
1376
1377 #[cfg(not(any(PyPy, GraalPy)))]
1378 fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> {
1379 let mut map = HashMap::<&'static str, i32>::new();
1380 map.insert("a", 1);
1381 map.insert("b", 2);
1382 map.insert("c", 3);
1383 map.into_py_dict(py).unwrap()
1384 }
1385
1386 #[test]
1387 #[cfg(not(any(PyPy, GraalPy)))]
1388 fn dict_keys_view() {
1389 Python::with_gil(|py| {
1390 let dict = abc_dict(py);
1391 let keys = dict.call_method0("keys").unwrap();
1392 assert!(keys.is_instance(&py.get_type::<PyDictKeys>()).unwrap());
1393 })
1394 }
1395
1396 #[test]
1397 #[cfg(not(any(PyPy, GraalPy)))]
1398 fn dict_values_view() {
1399 Python::with_gil(|py| {
1400 let dict = abc_dict(py);
1401 let values = dict.call_method0("values").unwrap();
1402 assert!(values.is_instance(&py.get_type::<PyDictValues>()).unwrap());
1403 })
1404 }
1405
1406 #[test]
1407 #[cfg(not(any(PyPy, GraalPy)))]
1408 fn dict_items_view() {
1409 Python::with_gil(|py| {
1410 let dict = abc_dict(py);
1411 let items = dict.call_method0("items").unwrap();
1412 assert!(items.is_instance(&py.get_type::<PyDictItems>()).unwrap());
1413 })
1414 }
1415
1416 #[test]
1417 fn dict_update() {
1418 Python::with_gil(|py| {
1419 let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1420 let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1421 dict.update(other.as_mapping()).unwrap();
1422 assert_eq!(dict.len(), 4);
1423 assert_eq!(
1424 dict.get_item("a")
1425 .unwrap()
1426 .unwrap()
1427 .extract::<i32>()
1428 .unwrap(),
1429 1
1430 );
1431 assert_eq!(
1432 dict.get_item("b")
1433 .unwrap()
1434 .unwrap()
1435 .extract::<i32>()
1436 .unwrap(),
1437 4
1438 );
1439 assert_eq!(
1440 dict.get_item("c")
1441 .unwrap()
1442 .unwrap()
1443 .extract::<i32>()
1444 .unwrap(),
1445 5
1446 );
1447 assert_eq!(
1448 dict.get_item("d")
1449 .unwrap()
1450 .unwrap()
1451 .extract::<i32>()
1452 .unwrap(),
1453 6
1454 );
1455
1456 assert_eq!(other.len(), 3);
1457 assert_eq!(
1458 other
1459 .get_item("b")
1460 .unwrap()
1461 .unwrap()
1462 .extract::<i32>()
1463 .unwrap(),
1464 4
1465 );
1466 assert_eq!(
1467 other
1468 .get_item("c")
1469 .unwrap()
1470 .unwrap()
1471 .extract::<i32>()
1472 .unwrap(),
1473 5
1474 );
1475 assert_eq!(
1476 other
1477 .get_item("d")
1478 .unwrap()
1479 .unwrap()
1480 .extract::<i32>()
1481 .unwrap(),
1482 6
1483 );
1484 })
1485 }
1486
1487 #[test]
1488 fn dict_update_if_missing() {
1489 Python::with_gil(|py| {
1490 let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1491 let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1492 dict.update_if_missing(other.as_mapping()).unwrap();
1493 assert_eq!(dict.len(), 4);
1494 assert_eq!(
1495 dict.get_item("a")
1496 .unwrap()
1497 .unwrap()
1498 .extract::<i32>()
1499 .unwrap(),
1500 1
1501 );
1502 assert_eq!(
1503 dict.get_item("b")
1504 .unwrap()
1505 .unwrap()
1506 .extract::<i32>()
1507 .unwrap(),
1508 2
1509 );
1510 assert_eq!(
1511 dict.get_item("c")
1512 .unwrap()
1513 .unwrap()
1514 .extract::<i32>()
1515 .unwrap(),
1516 3
1517 );
1518 assert_eq!(
1519 dict.get_item("d")
1520 .unwrap()
1521 .unwrap()
1522 .extract::<i32>()
1523 .unwrap(),
1524 6
1525 );
1526
1527 assert_eq!(other.len(), 3);
1528 assert_eq!(
1529 other
1530 .get_item("b")
1531 .unwrap()
1532 .unwrap()
1533 .extract::<i32>()
1534 .unwrap(),
1535 4
1536 );
1537 assert_eq!(
1538 other
1539 .get_item("c")
1540 .unwrap()
1541 .unwrap()
1542 .extract::<i32>()
1543 .unwrap(),
1544 5
1545 );
1546 assert_eq!(
1547 other
1548 .get_item("d")
1549 .unwrap()
1550 .unwrap()
1551 .extract::<i32>()
1552 .unwrap(),
1553 6
1554 );
1555 })
1556 }
1557
1558 #[test]
1559 fn test_iter_all() {
1560 Python::with_gil(|py| {
1561 let dict = [(1, true), (2, true), (3, true)].into_py_dict(py).unwrap();
1562 assert!(dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1563
1564 let dict = [(1, true), (2, false), (3, true)].into_py_dict(py).unwrap();
1565 assert!(!dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1566 });
1567 }
1568
1569 #[test]
1570 fn test_iter_any() {
1571 Python::with_gil(|py| {
1572 let dict = [(1, true), (2, false), (3, false)]
1573 .into_py_dict(py)
1574 .unwrap();
1575 assert!(dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1576
1577 let dict = [(1, false), (2, false), (3, false)]
1578 .into_py_dict(py)
1579 .unwrap();
1580 assert!(!dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1581 });
1582 }
1583
1584 #[test]
1585 #[allow(clippy::search_is_some)]
1586 fn test_iter_find() {
1587 Python::with_gil(|py| {
1588 let dict = [(1, false), (2, true), (3, false)]
1589 .into_py_dict(py)
1590 .unwrap();
1591
1592 assert_eq!(
1593 Some((2, true)),
1594 dict.iter()
1595 .find(|(_, v)| v.extract::<bool>().unwrap())
1596 .map(|(k, v)| (k.extract().unwrap(), v.extract().unwrap()))
1597 );
1598
1599 let dict = [(1, false), (2, false), (3, false)]
1600 .into_py_dict(py)
1601 .unwrap();
1602
1603 assert!(dict
1604 .iter()
1605 .find(|(_, v)| v.extract::<bool>().unwrap())
1606 .is_none());
1607 });
1608 }
1609
1610 #[test]
1611 #[allow(clippy::search_is_some)]
1612 fn test_iter_position() {
1613 Python::with_gil(|py| {
1614 let dict = [(1, false), (2, false), (3, true)]
1615 .into_py_dict(py)
1616 .unwrap();
1617 assert_eq!(
1618 Some(2),
1619 dict.iter().position(|(_, v)| v.extract::<bool>().unwrap())
1620 );
1621
1622 let dict = [(1, false), (2, false), (3, false)]
1623 .into_py_dict(py)
1624 .unwrap();
1625 assert!(dict
1626 .iter()
1627 .position(|(_, v)| v.extract::<bool>().unwrap())
1628 .is_none());
1629 });
1630 }
1631
1632 #[test]
1633 fn test_iter_fold() {
1634 Python::with_gil(|py| {
1635 let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1636 let sum = dict
1637 .iter()
1638 .fold(0, |acc, (_, v)| acc + v.extract::<i32>().unwrap());
1639 assert_eq!(sum, 6);
1640 });
1641 }
1642
1643 #[test]
1644 fn test_iter_try_fold() {
1645 Python::with_gil(|py| {
1646 let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1647 let sum = dict
1648 .iter()
1649 .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1650 .unwrap();
1651 assert_eq!(sum, 6);
1652
1653 let dict = [(1, "foo"), (2, "bar")].into_py_dict(py).unwrap();
1654 assert!(dict
1655 .iter()
1656 .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1657 .is_err());
1658 });
1659 }
1660}