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 fn count(self) -> usize
539 where
540 Self: Sized,
541 {
542 self.len()
543 }
544
545 #[inline]
546 #[cfg(Py_GIL_DISABLED)]
547 fn fold<B, F>(mut self, init: B, mut f: F) -> B
548 where
549 Self: Sized,
550 F: FnMut(B, Self::Item) -> B,
551 {
552 self.inner.with_critical_section(&self.dict, |inner| {
553 let mut accum = init;
554 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
555 accum = f(accum, x);
556 }
557 accum
558 })
559 }
560
561 #[inline]
562 #[cfg(all(Py_GIL_DISABLED, feature = "nightly"))]
563 fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
564 where
565 Self: Sized,
566 F: FnMut(B, Self::Item) -> R,
567 R: std::ops::Try<Output = B>,
568 {
569 self.inner.with_critical_section(&self.dict, |inner| {
570 let mut accum = init;
571 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
572 accum = f(accum, x)?
573 }
574 R::from_output(accum)
575 })
576 }
577
578 #[inline]
579 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
580 fn all<F>(&mut self, mut f: F) -> bool
581 where
582 Self: Sized,
583 F: FnMut(Self::Item) -> bool,
584 {
585 self.inner.with_critical_section(&self.dict, |inner| {
586 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
587 if !f(x) {
588 return false;
589 }
590 }
591 true
592 })
593 }
594
595 #[inline]
596 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
597 fn any<F>(&mut self, mut f: F) -> bool
598 where
599 Self: Sized,
600 F: FnMut(Self::Item) -> bool,
601 {
602 self.inner.with_critical_section(&self.dict, |inner| {
603 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
604 if f(x) {
605 return true;
606 }
607 }
608 false
609 })
610 }
611
612 #[inline]
613 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
614 fn find<P>(&mut self, mut predicate: P) -> Option<Self::Item>
615 where
616 Self: Sized,
617 P: FnMut(&Self::Item) -> bool,
618 {
619 self.inner.with_critical_section(&self.dict, |inner| {
620 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
621 if predicate(&x) {
622 return Some(x);
623 }
624 }
625 None
626 })
627 }
628
629 #[inline]
630 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
631 fn find_map<B, F>(&mut self, mut f: F) -> Option<B>
632 where
633 Self: Sized,
634 F: FnMut(Self::Item) -> Option<B>,
635 {
636 self.inner.with_critical_section(&self.dict, |inner| {
637 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
638 if let found @ Some(_) = f(x) {
639 return found;
640 }
641 }
642 None
643 })
644 }
645
646 #[inline]
647 #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
648 fn position<P>(&mut self, mut predicate: P) -> Option<usize>
649 where
650 Self: Sized,
651 P: FnMut(Self::Item) -> bool,
652 {
653 self.inner.with_critical_section(&self.dict, |inner| {
654 let mut acc = 0;
655 while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
656 if predicate(x) {
657 return Some(acc);
658 }
659 acc += 1;
660 }
661 None
662 })
663 }
664}
665
666impl ExactSizeIterator for BoundDictIterator<'_> {
667 fn len(&self) -> usize {
668 match self.inner {
669 DictIterImpl::DictIter { remaining, .. } => remaining as usize,
670 }
671 }
672}
673
674impl<'py> BoundDictIterator<'py> {
675 fn new(dict: Bound<'py, PyDict>) -> Self {
676 let remaining = dict_len(&dict);
677
678 Self {
679 dict,
680 inner: DictIterImpl::DictIter {
681 ppos: 0,
682 di_used: remaining,
683 remaining,
684 },
685 }
686 }
687}
688
689impl<'py> IntoIterator for Bound<'py, PyDict> {
690 type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
691 type IntoIter = BoundDictIterator<'py>;
692
693 fn into_iter(self) -> Self::IntoIter {
694 BoundDictIterator::new(self)
695 }
696}
697
698impl<'py> IntoIterator for &Bound<'py, PyDict> {
699 type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
700 type IntoIter = BoundDictIterator<'py>;
701
702 fn into_iter(self) -> Self::IntoIter {
703 self.iter()
704 }
705}
706
707mod borrowed_iter {
708 use super::*;
709
710 pub struct BorrowedDictIter<'a, 'py> {
714 dict: Borrowed<'a, 'py, PyDict>,
715 ppos: ffi::Py_ssize_t,
716 len: ffi::Py_ssize_t,
717 }
718
719 impl<'a, 'py> Iterator for BorrowedDictIter<'a, 'py> {
720 type Item = (Borrowed<'a, 'py, PyAny>, Borrowed<'a, 'py, PyAny>);
721
722 #[inline]
723 fn next(&mut self) -> Option<Self::Item> {
724 let mut key: *mut ffi::PyObject = std::ptr::null_mut();
725 let mut value: *mut ffi::PyObject = std::ptr::null_mut();
726
727 if unsafe { ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) }
729 != 0
730 {
731 let py = self.dict.py();
732 self.len -= 1;
733 Some(unsafe { (key.assume_borrowed(py), value.assume_borrowed(py)) })
737 } else {
738 None
739 }
740 }
741
742 #[inline]
743 fn size_hint(&self) -> (usize, Option<usize>) {
744 let len = self.len();
745 (len, Some(len))
746 }
747
748 #[inline]
749 fn count(self) -> usize
750 where
751 Self: Sized,
752 {
753 self.len()
754 }
755 }
756
757 impl ExactSizeIterator for BorrowedDictIter<'_, '_> {
758 fn len(&self) -> usize {
759 self.len as usize
760 }
761 }
762
763 impl<'a, 'py> BorrowedDictIter<'a, 'py> {
764 pub(super) fn new(dict: Borrowed<'a, 'py, PyDict>) -> Self {
765 let len = dict_len(&dict);
766 BorrowedDictIter { dict, ppos: 0, len }
767 }
768 }
769}
770
771pub(crate) use borrowed_iter::BorrowedDictIter;
772
773pub trait IntoPyDict<'py>: Sized {
776 fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>>;
779
780 #[deprecated(since = "0.23.0", note = "renamed to `IntoPyDict::into_py_dict`")]
782 #[inline]
783 fn into_py_dict_bound(self, py: Python<'py>) -> Bound<'py, PyDict> {
784 self.into_py_dict(py).unwrap()
785 }
786}
787
788impl<'py, T, I> IntoPyDict<'py> for I
789where
790 T: PyDictItem<'py>,
791 I: IntoIterator<Item = T>,
792{
793 fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
794 let dict = PyDict::new(py);
795 self.into_iter().try_for_each(|item| {
796 let (key, value) = item.unpack();
797 dict.set_item(key, value)
798 })?;
799 Ok(dict)
800 }
801}
802
803trait PyDictItem<'py> {
805 type K: IntoPyObject<'py>;
806 type V: IntoPyObject<'py>;
807 fn unpack(self) -> (Self::K, Self::V);
808}
809
810impl<'py, K, V> PyDictItem<'py> for (K, V)
811where
812 K: IntoPyObject<'py>,
813 V: IntoPyObject<'py>,
814{
815 type K = K;
816 type V = V;
817
818 fn unpack(self) -> (Self::K, Self::V) {
819 (self.0, self.1)
820 }
821}
822
823impl<'a, 'py, K, V> PyDictItem<'py> for &'a (K, V)
824where
825 &'a K: IntoPyObject<'py>,
826 &'a V: IntoPyObject<'py>,
827{
828 type K = &'a K;
829 type V = &'a V;
830
831 fn unpack(self) -> (Self::K, Self::V) {
832 (&self.0, &self.1)
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839 use crate::types::PyTuple;
840 use std::collections::{BTreeMap, HashMap};
841
842 #[test]
843 fn test_new() {
844 Python::with_gil(|py| {
845 let dict = [(7, 32)].into_py_dict(py).unwrap();
846 assert_eq!(
847 32,
848 dict.get_item(7i32)
849 .unwrap()
850 .unwrap()
851 .extract::<i32>()
852 .unwrap()
853 );
854 assert!(dict.get_item(8i32).unwrap().is_none());
855 let map: HashMap<i32, i32> = [(7, 32)].iter().cloned().collect();
856 assert_eq!(map, dict.extract().unwrap());
857 let map: BTreeMap<i32, i32> = [(7, 32)].iter().cloned().collect();
858 assert_eq!(map, dict.extract().unwrap());
859 });
860 }
861
862 #[test]
863 #[cfg(not(any(PyPy, GraalPy)))]
864 fn test_from_sequence() {
865 Python::with_gil(|py| {
866 let items = PyList::new(py, vec![("a", 1), ("b", 2)]).unwrap();
867 let dict = PyDict::from_sequence(&items).unwrap();
868 assert_eq!(
869 1,
870 dict.get_item("a")
871 .unwrap()
872 .unwrap()
873 .extract::<i32>()
874 .unwrap()
875 );
876 assert_eq!(
877 2,
878 dict.get_item("b")
879 .unwrap()
880 .unwrap()
881 .extract::<i32>()
882 .unwrap()
883 );
884 let map: HashMap<String, i32> =
885 [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
886 assert_eq!(map, dict.extract().unwrap());
887 let map: BTreeMap<String, i32> =
888 [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
889 assert_eq!(map, dict.extract().unwrap());
890 });
891 }
892
893 #[test]
894 #[cfg(not(any(PyPy, GraalPy)))]
895 fn test_from_sequence_err() {
896 Python::with_gil(|py| {
897 let items = PyList::new(py, vec!["a", "b"]).unwrap();
898 assert!(PyDict::from_sequence(&items).is_err());
899 });
900 }
901
902 #[test]
903 fn test_copy() {
904 Python::with_gil(|py| {
905 let dict = [(7, 32)].into_py_dict(py).unwrap();
906
907 let ndict = dict.copy().unwrap();
908 assert_eq!(
909 32,
910 ndict
911 .get_item(7i32)
912 .unwrap()
913 .unwrap()
914 .extract::<i32>()
915 .unwrap()
916 );
917 assert!(ndict.get_item(8i32).unwrap().is_none());
918 });
919 }
920
921 #[test]
922 fn test_len() {
923 Python::with_gil(|py| {
924 let mut v = HashMap::<i32, i32>::new();
925 let dict = (&v).into_pyobject(py).unwrap();
926 assert_eq!(0, dict.len());
927 v.insert(7, 32);
928 let dict2 = v.into_pyobject(py).unwrap();
929 assert_eq!(1, dict2.len());
930 });
931 }
932
933 #[test]
934 fn test_contains() {
935 Python::with_gil(|py| {
936 let mut v = HashMap::new();
937 v.insert(7, 32);
938 let dict = v.into_pyobject(py).unwrap();
939 assert!(dict.contains(7i32).unwrap());
940 assert!(!dict.contains(8i32).unwrap());
941 });
942 }
943
944 #[test]
945 fn test_get_item() {
946 Python::with_gil(|py| {
947 let mut v = HashMap::new();
948 v.insert(7, 32);
949 let dict = v.into_pyobject(py).unwrap();
950 assert_eq!(
951 32,
952 dict.get_item(7i32)
953 .unwrap()
954 .unwrap()
955 .extract::<i32>()
956 .unwrap()
957 );
958 assert!(dict.get_item(8i32).unwrap().is_none());
959 });
960 }
961
962 #[cfg(feature = "macros")]
963 #[test]
964 fn test_get_item_error_path() {
965 use crate::exceptions::PyTypeError;
966
967 #[crate::pyclass(crate = "crate")]
968 struct HashErrors;
969
970 #[crate::pymethods(crate = "crate")]
971 impl HashErrors {
972 #[new]
973 fn new() -> Self {
974 HashErrors {}
975 }
976
977 fn __hash__(&self) -> PyResult<isize> {
978 Err(PyTypeError::new_err("Error from __hash__"))
979 }
980 }
981
982 Python::with_gil(|py| {
983 let class = py.get_type::<HashErrors>();
984 let instance = class.call0().unwrap();
985 let d = PyDict::new(py);
986 match d.get_item(instance) {
987 Ok(_) => {
988 panic!("this get_item call should always error")
989 }
990 Err(err) => {
991 assert!(err.is_instance_of::<PyTypeError>(py));
992 assert_eq!(err.value(py).to_string(), "Error from __hash__")
993 }
994 }
995 })
996 }
997
998 #[test]
999 fn test_set_item() {
1000 Python::with_gil(|py| {
1001 let mut v = HashMap::new();
1002 v.insert(7, 32);
1003 let dict = v.into_pyobject(py).unwrap();
1004 assert!(dict.set_item(7i32, 42i32).is_ok()); assert!(dict.set_item(8i32, 123i32).is_ok()); assert_eq!(
1007 42i32,
1008 dict.get_item(7i32)
1009 .unwrap()
1010 .unwrap()
1011 .extract::<i32>()
1012 .unwrap()
1013 );
1014 assert_eq!(
1015 123i32,
1016 dict.get_item(8i32)
1017 .unwrap()
1018 .unwrap()
1019 .extract::<i32>()
1020 .unwrap()
1021 );
1022 });
1023 }
1024
1025 #[test]
1026 fn test_set_item_refcnt() {
1027 Python::with_gil(|py| {
1028 let cnt;
1029 let obj = py.eval(ffi::c_str!("object()"), None, None).unwrap();
1030 {
1031 cnt = obj.get_refcnt();
1032 let _dict = [(10, &obj)].into_py_dict(py);
1033 }
1034 {
1035 assert_eq!(cnt, obj.get_refcnt());
1036 }
1037 });
1038 }
1039
1040 #[test]
1041 fn test_set_item_does_not_update_original_object() {
1042 Python::with_gil(|py| {
1043 let mut v = HashMap::new();
1044 v.insert(7, 32);
1045 let dict = (&v).into_pyobject(py).unwrap();
1046 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));
1050 });
1051 }
1052
1053 #[test]
1054 fn test_del_item() {
1055 Python::with_gil(|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());
1060 assert_eq!(0, dict.len());
1061 assert!(dict.get_item(7i32).unwrap().is_none());
1062 });
1063 }
1064
1065 #[test]
1066 fn test_del_item_does_not_update_original_object() {
1067 Python::with_gil(|py| {
1068 let mut v = HashMap::new();
1069 v.insert(7, 32);
1070 let dict = (&v).into_pyobject(py).unwrap();
1071 assert!(dict.del_item(7i32).is_ok()); assert_eq!(32i32, *v.get(&7i32).unwrap()); });
1074 }
1075
1076 #[test]
1077 fn test_items() {
1078 Python::with_gil(|py| {
1079 let mut v = HashMap::new();
1080 v.insert(7, 32);
1081 v.insert(8, 42);
1082 v.insert(9, 123);
1083 let dict = v.into_pyobject(py).unwrap();
1084 let mut key_sum = 0;
1086 let mut value_sum = 0;
1087 for el in dict.items() {
1088 let tuple = el.downcast::<PyTuple>().unwrap();
1089 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
1090 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
1091 }
1092 assert_eq!(7 + 8 + 9, key_sum);
1093 assert_eq!(32 + 42 + 123, value_sum);
1094 });
1095 }
1096
1097 #[test]
1098 fn test_keys() {
1099 Python::with_gil(|py| {
1100 let mut v = HashMap::new();
1101 v.insert(7, 32);
1102 v.insert(8, 42);
1103 v.insert(9, 123);
1104 let dict = v.into_pyobject(py).unwrap();
1105 let mut key_sum = 0;
1107 for el in dict.keys() {
1108 key_sum += el.extract::<i32>().unwrap();
1109 }
1110 assert_eq!(7 + 8 + 9, key_sum);
1111 });
1112 }
1113
1114 #[test]
1115 fn test_values() {
1116 Python::with_gil(|py| {
1117 let mut v = HashMap::new();
1118 v.insert(7, 32);
1119 v.insert(8, 42);
1120 v.insert(9, 123);
1121 let dict = v.into_pyobject(py).unwrap();
1122 let mut values_sum = 0;
1124 for el in dict.values() {
1125 values_sum += el.extract::<i32>().unwrap();
1126 }
1127 assert_eq!(32 + 42 + 123, values_sum);
1128 });
1129 }
1130
1131 #[test]
1132 fn test_iter() {
1133 Python::with_gil(|py| {
1134 let mut v = HashMap::new();
1135 v.insert(7, 32);
1136 v.insert(8, 42);
1137 v.insert(9, 123);
1138 let dict = v.into_pyobject(py).unwrap();
1139 let mut key_sum = 0;
1140 let mut value_sum = 0;
1141 for (key, value) in dict {
1142 key_sum += key.extract::<i32>().unwrap();
1143 value_sum += value.extract::<i32>().unwrap();
1144 }
1145 assert_eq!(7 + 8 + 9, key_sum);
1146 assert_eq!(32 + 42 + 123, value_sum);
1147 });
1148 }
1149
1150 #[test]
1151 fn test_iter_bound() {
1152 Python::with_gil(|py| {
1153 let mut v = HashMap::new();
1154 v.insert(7, 32);
1155 v.insert(8, 42);
1156 v.insert(9, 123);
1157 let dict = v.into_pyobject(py).unwrap();
1158 let mut key_sum = 0;
1159 let mut value_sum = 0;
1160 for (key, value) in dict {
1161 key_sum += key.extract::<i32>().unwrap();
1162 value_sum += value.extract::<i32>().unwrap();
1163 }
1164 assert_eq!(7 + 8 + 9, key_sum);
1165 assert_eq!(32 + 42 + 123, value_sum);
1166 });
1167 }
1168
1169 #[test]
1170 fn test_iter_value_mutated() {
1171 Python::with_gil(|py| {
1172 let mut v = HashMap::new();
1173 v.insert(7, 32);
1174 v.insert(8, 42);
1175 v.insert(9, 123);
1176
1177 let dict = (&v).into_pyobject(py).unwrap();
1178
1179 for (key, value) in &dict {
1180 dict.set_item(key, value.extract::<i32>().unwrap() + 7)
1181 .unwrap();
1182 }
1183 });
1184 }
1185
1186 #[test]
1187 #[should_panic]
1188 fn test_iter_key_mutated() {
1189 Python::with_gil(|py| {
1190 let mut v = HashMap::new();
1191 for i in 0..10 {
1192 v.insert(i * 2, i * 2);
1193 }
1194 let dict = v.into_pyobject(py).unwrap();
1195
1196 for (i, (key, value)) in dict.iter().enumerate() {
1197 let key = key.extract::<i32>().unwrap();
1198 let value = value.extract::<i32>().unwrap();
1199
1200 dict.set_item(key + 1, value + 1).unwrap();
1201
1202 if i > 1000 {
1203 break;
1205 };
1206 }
1207 });
1208 }
1209
1210 #[test]
1211 #[should_panic]
1212 fn test_iter_key_mutated_constant_len() {
1213 Python::with_gil(|py| {
1214 let mut v = HashMap::new();
1215 for i in 0..10 {
1216 v.insert(i * 2, i * 2);
1217 }
1218 let dict = v.into_pyobject(py).unwrap();
1219
1220 for (i, (key, value)) in dict.iter().enumerate() {
1221 let key = key.extract::<i32>().unwrap();
1222 let value = value.extract::<i32>().unwrap();
1223 dict.del_item(key).unwrap();
1224 dict.set_item(key + 1, value + 1).unwrap();
1225
1226 if i > 1000 {
1227 break;
1229 };
1230 }
1231 });
1232 }
1233
1234 #[test]
1235 fn test_iter_size_hint() {
1236 Python::with_gil(|py| {
1237 let mut v = HashMap::new();
1238 v.insert(7, 32);
1239 v.insert(8, 42);
1240 v.insert(9, 123);
1241 let dict = (&v).into_pyobject(py).unwrap();
1242
1243 let mut iter = dict.iter();
1244 assert_eq!(iter.size_hint(), (v.len(), Some(v.len())));
1245 iter.next();
1246 assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1)));
1247
1248 for _ in &mut iter {}
1250
1251 assert_eq!(iter.size_hint(), (0, Some(0)));
1252
1253 assert!(iter.next().is_none());
1254
1255 assert_eq!(iter.size_hint(), (0, Some(0)));
1256 });
1257 }
1258
1259 #[test]
1260 fn test_into_iter() {
1261 Python::with_gil(|py| {
1262 let mut v = HashMap::new();
1263 v.insert(7, 32);
1264 v.insert(8, 42);
1265 v.insert(9, 123);
1266 let dict = v.into_pyobject(py).unwrap();
1267 let mut key_sum = 0;
1268 let mut value_sum = 0;
1269 for (key, value) in dict {
1270 key_sum += key.extract::<i32>().unwrap();
1271 value_sum += value.extract::<i32>().unwrap();
1272 }
1273 assert_eq!(7 + 8 + 9, key_sum);
1274 assert_eq!(32 + 42 + 123, value_sum);
1275 });
1276 }
1277
1278 #[test]
1279 fn test_hashmap_into_dict() {
1280 Python::with_gil(|py| {
1281 let mut map = HashMap::<i32, i32>::new();
1282 map.insert(1, 1);
1283
1284 let py_map = map.into_py_dict(py).unwrap();
1285
1286 assert_eq!(py_map.len(), 1);
1287 assert_eq!(
1288 py_map
1289 .get_item(1)
1290 .unwrap()
1291 .unwrap()
1292 .extract::<i32>()
1293 .unwrap(),
1294 1
1295 );
1296 });
1297 }
1298
1299 #[test]
1300 fn test_btreemap_into_dict() {
1301 Python::with_gil(|py| {
1302 let mut map = BTreeMap::<i32, i32>::new();
1303 map.insert(1, 1);
1304
1305 let py_map = map.into_py_dict(py).unwrap();
1306
1307 assert_eq!(py_map.len(), 1);
1308 assert_eq!(
1309 py_map
1310 .get_item(1)
1311 .unwrap()
1312 .unwrap()
1313 .extract::<i32>()
1314 .unwrap(),
1315 1
1316 );
1317 });
1318 }
1319
1320 #[test]
1321 fn test_vec_into_dict() {
1322 Python::with_gil(|py| {
1323 let vec = vec![("a", 1), ("b", 2), ("c", 3)];
1324 let py_map = vec.into_py_dict(py).unwrap();
1325
1326 assert_eq!(py_map.len(), 3);
1327 assert_eq!(
1328 py_map
1329 .get_item("b")
1330 .unwrap()
1331 .unwrap()
1332 .extract::<i32>()
1333 .unwrap(),
1334 2
1335 );
1336 });
1337 }
1338
1339 #[test]
1340 fn test_slice_into_dict() {
1341 Python::with_gil(|py| {
1342 let arr = [("a", 1), ("b", 2), ("c", 3)];
1343 let py_map = arr.into_py_dict(py).unwrap();
1344
1345 assert_eq!(py_map.len(), 3);
1346 assert_eq!(
1347 py_map
1348 .get_item("b")
1349 .unwrap()
1350 .unwrap()
1351 .extract::<i32>()
1352 .unwrap(),
1353 2
1354 );
1355 });
1356 }
1357
1358 #[test]
1359 fn dict_as_mapping() {
1360 Python::with_gil(|py| {
1361 let mut map = HashMap::<i32, i32>::new();
1362 map.insert(1, 1);
1363
1364 let py_map = map.into_py_dict(py).unwrap();
1365
1366 assert_eq!(py_map.as_mapping().len().unwrap(), 1);
1367 assert_eq!(
1368 py_map
1369 .as_mapping()
1370 .get_item(1)
1371 .unwrap()
1372 .extract::<i32>()
1373 .unwrap(),
1374 1
1375 );
1376 });
1377 }
1378
1379 #[test]
1380 fn dict_into_mapping() {
1381 Python::with_gil(|py| {
1382 let mut map = HashMap::<i32, i32>::new();
1383 map.insert(1, 1);
1384
1385 let py_map = map.into_py_dict(py).unwrap();
1386
1387 let py_mapping = py_map.into_mapping();
1388 assert_eq!(py_mapping.len().unwrap(), 1);
1389 assert_eq!(py_mapping.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
1390 });
1391 }
1392
1393 #[cfg(not(any(PyPy, GraalPy)))]
1394 fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> {
1395 let mut map = HashMap::<&'static str, i32>::new();
1396 map.insert("a", 1);
1397 map.insert("b", 2);
1398 map.insert("c", 3);
1399 map.into_py_dict(py).unwrap()
1400 }
1401
1402 #[test]
1403 #[cfg(not(any(PyPy, GraalPy)))]
1404 fn dict_keys_view() {
1405 Python::with_gil(|py| {
1406 let dict = abc_dict(py);
1407 let keys = dict.call_method0("keys").unwrap();
1408 assert!(keys.is_instance(&py.get_type::<PyDictKeys>()).unwrap());
1409 })
1410 }
1411
1412 #[test]
1413 #[cfg(not(any(PyPy, GraalPy)))]
1414 fn dict_values_view() {
1415 Python::with_gil(|py| {
1416 let dict = abc_dict(py);
1417 let values = dict.call_method0("values").unwrap();
1418 assert!(values.is_instance(&py.get_type::<PyDictValues>()).unwrap());
1419 })
1420 }
1421
1422 #[test]
1423 #[cfg(not(any(PyPy, GraalPy)))]
1424 fn dict_items_view() {
1425 Python::with_gil(|py| {
1426 let dict = abc_dict(py);
1427 let items = dict.call_method0("items").unwrap();
1428 assert!(items.is_instance(&py.get_type::<PyDictItems>()).unwrap());
1429 })
1430 }
1431
1432 #[test]
1433 fn dict_update() {
1434 Python::with_gil(|py| {
1435 let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1436 let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1437 dict.update(other.as_mapping()).unwrap();
1438 assert_eq!(dict.len(), 4);
1439 assert_eq!(
1440 dict.get_item("a")
1441 .unwrap()
1442 .unwrap()
1443 .extract::<i32>()
1444 .unwrap(),
1445 1
1446 );
1447 assert_eq!(
1448 dict.get_item("b")
1449 .unwrap()
1450 .unwrap()
1451 .extract::<i32>()
1452 .unwrap(),
1453 4
1454 );
1455 assert_eq!(
1456 dict.get_item("c")
1457 .unwrap()
1458 .unwrap()
1459 .extract::<i32>()
1460 .unwrap(),
1461 5
1462 );
1463 assert_eq!(
1464 dict.get_item("d")
1465 .unwrap()
1466 .unwrap()
1467 .extract::<i32>()
1468 .unwrap(),
1469 6
1470 );
1471
1472 assert_eq!(other.len(), 3);
1473 assert_eq!(
1474 other
1475 .get_item("b")
1476 .unwrap()
1477 .unwrap()
1478 .extract::<i32>()
1479 .unwrap(),
1480 4
1481 );
1482 assert_eq!(
1483 other
1484 .get_item("c")
1485 .unwrap()
1486 .unwrap()
1487 .extract::<i32>()
1488 .unwrap(),
1489 5
1490 );
1491 assert_eq!(
1492 other
1493 .get_item("d")
1494 .unwrap()
1495 .unwrap()
1496 .extract::<i32>()
1497 .unwrap(),
1498 6
1499 );
1500 })
1501 }
1502
1503 #[test]
1504 fn dict_update_if_missing() {
1505 Python::with_gil(|py| {
1506 let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1507 let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1508 dict.update_if_missing(other.as_mapping()).unwrap();
1509 assert_eq!(dict.len(), 4);
1510 assert_eq!(
1511 dict.get_item("a")
1512 .unwrap()
1513 .unwrap()
1514 .extract::<i32>()
1515 .unwrap(),
1516 1
1517 );
1518 assert_eq!(
1519 dict.get_item("b")
1520 .unwrap()
1521 .unwrap()
1522 .extract::<i32>()
1523 .unwrap(),
1524 2
1525 );
1526 assert_eq!(
1527 dict.get_item("c")
1528 .unwrap()
1529 .unwrap()
1530 .extract::<i32>()
1531 .unwrap(),
1532 3
1533 );
1534 assert_eq!(
1535 dict.get_item("d")
1536 .unwrap()
1537 .unwrap()
1538 .extract::<i32>()
1539 .unwrap(),
1540 6
1541 );
1542
1543 assert_eq!(other.len(), 3);
1544 assert_eq!(
1545 other
1546 .get_item("b")
1547 .unwrap()
1548 .unwrap()
1549 .extract::<i32>()
1550 .unwrap(),
1551 4
1552 );
1553 assert_eq!(
1554 other
1555 .get_item("c")
1556 .unwrap()
1557 .unwrap()
1558 .extract::<i32>()
1559 .unwrap(),
1560 5
1561 );
1562 assert_eq!(
1563 other
1564 .get_item("d")
1565 .unwrap()
1566 .unwrap()
1567 .extract::<i32>()
1568 .unwrap(),
1569 6
1570 );
1571 })
1572 }
1573
1574 #[test]
1575 fn test_iter_all() {
1576 Python::with_gil(|py| {
1577 let dict = [(1, true), (2, true), (3, true)].into_py_dict(py).unwrap();
1578 assert!(dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1579
1580 let dict = [(1, true), (2, false), (3, true)].into_py_dict(py).unwrap();
1581 assert!(!dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1582 });
1583 }
1584
1585 #[test]
1586 fn test_iter_any() {
1587 Python::with_gil(|py| {
1588 let dict = [(1, true), (2, false), (3, false)]
1589 .into_py_dict(py)
1590 .unwrap();
1591 assert!(dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1592
1593 let dict = [(1, false), (2, false), (3, false)]
1594 .into_py_dict(py)
1595 .unwrap();
1596 assert!(!dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1597 });
1598 }
1599
1600 #[test]
1601 #[allow(clippy::search_is_some)]
1602 fn test_iter_find() {
1603 Python::with_gil(|py| {
1604 let dict = [(1, false), (2, true), (3, false)]
1605 .into_py_dict(py)
1606 .unwrap();
1607
1608 assert_eq!(
1609 Some((2, true)),
1610 dict.iter()
1611 .find(|(_, v)| v.extract::<bool>().unwrap())
1612 .map(|(k, v)| (k.extract().unwrap(), v.extract().unwrap()))
1613 );
1614
1615 let dict = [(1, false), (2, false), (3, false)]
1616 .into_py_dict(py)
1617 .unwrap();
1618
1619 assert!(dict
1620 .iter()
1621 .find(|(_, v)| v.extract::<bool>().unwrap())
1622 .is_none());
1623 });
1624 }
1625
1626 #[test]
1627 #[allow(clippy::search_is_some)]
1628 fn test_iter_position() {
1629 Python::with_gil(|py| {
1630 let dict = [(1, false), (2, false), (3, true)]
1631 .into_py_dict(py)
1632 .unwrap();
1633 assert_eq!(
1634 Some(2),
1635 dict.iter().position(|(_, v)| v.extract::<bool>().unwrap())
1636 );
1637
1638 let dict = [(1, false), (2, false), (3, false)]
1639 .into_py_dict(py)
1640 .unwrap();
1641 assert!(dict
1642 .iter()
1643 .position(|(_, v)| v.extract::<bool>().unwrap())
1644 .is_none());
1645 });
1646 }
1647
1648 #[test]
1649 fn test_iter_fold() {
1650 Python::with_gil(|py| {
1651 let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1652 let sum = dict
1653 .iter()
1654 .fold(0, |acc, (_, v)| acc + v.extract::<i32>().unwrap());
1655 assert_eq!(sum, 6);
1656 });
1657 }
1658
1659 #[test]
1660 fn test_iter_try_fold() {
1661 Python::with_gil(|py| {
1662 let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1663 let sum = dict
1664 .iter()
1665 .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1666 .unwrap();
1667 assert_eq!(sum, 6);
1668
1669 let dict = [(1, "foo"), (2, "bar")].into_py_dict(py).unwrap();
1670 assert!(dict
1671 .iter()
1672 .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1673 .is_err());
1674 });
1675 }
1676
1677 #[test]
1678 fn test_iter_count() {
1679 Python::with_gil(|py| {
1680 let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1681 assert_eq!(dict.iter().count(), 3);
1682 })
1683 }
1684}