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