pyo3/types/
dict.rs

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/// Represents a Python `dict`.
10///
11/// Values of this type are accessed via PyO3's smart pointers, e.g. as
12/// [`Py<PyDict>`][crate::Py] or [`Bound<'py, PyDict>`][Bound].
13///
14/// For APIs available on `dict` objects, see the [`PyDictMethods`] trait which is implemented for
15/// [`Bound<'py, PyDict>`][Bound].
16#[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/// Represents a Python `dict_keys`.
29#[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/// Represents a Python `dict_values`.
41#[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/// Represents a Python `dict_items`.
53#[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    /// Creates a new empty dictionary.
66    pub fn new(py: Python<'_>) -> Bound<'_, PyDict> {
67        unsafe { ffi::PyDict_New().assume_owned(py).downcast_into_unchecked() }
68    }
69
70    /// Creates a new dictionary from the sequence given.
71    ///
72    /// The sequence must consist of `(PyObject, PyObject)`. This is
73    /// equivalent to `dict([("a", 1), ("b", 2)])`.
74    ///
75    /// Returns an error on invalid input. In the case of key collisions,
76    /// this keeps the last entry seen.
77    #[cfg(not(any(PyPy, GraalPy)))]
78    pub fn from_sequence<'py>(seq: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyDict>> {
79        let py = seq.py();
80        let dict = Self::new(py);
81        err::error_on_minusone(py, unsafe {
82            ffi::PyDict_MergeFromSeq2(dict.as_ptr(), seq.as_ptr(), 1)
83        })?;
84        Ok(dict)
85    }
86}
87
88/// Implementation of functionality for [`PyDict`].
89///
90/// These methods are defined for the `Bound<'py, PyDict>` smart pointer, so to use method call
91/// syntax these methods are separated into a trait, because stable Rust does not yet support
92/// `arbitrary_self_types`.
93#[doc(alias = "PyDict")]
94pub trait PyDictMethods<'py>: crate::sealed::Sealed {
95    /// Returns a new dictionary that contains the same key-value pairs as self.
96    ///
97    /// This is equivalent to the Python expression `self.copy()`.
98    fn copy(&self) -> PyResult<Bound<'py, PyDict>>;
99
100    /// Empties an existing dictionary of all key-value pairs.
101    fn clear(&self);
102
103    /// Return the number of items in the dictionary.
104    ///
105    /// This is equivalent to the Python expression `len(self)`.
106    fn len(&self) -> usize;
107
108    /// Checks if the dict is empty, i.e. `len(self) == 0`.
109    fn is_empty(&self) -> bool;
110
111    /// Determines if the dictionary contains the specified key.
112    ///
113    /// This is equivalent to the Python expression `key in self`.
114    fn contains<K>(&self, key: K) -> PyResult<bool>
115    where
116        K: IntoPyObject<'py>;
117
118    /// Gets an item from the dictionary.
119    ///
120    /// Returns `None` if the item is not present, or if an error occurs.
121    ///
122    /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`.
123    fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
124    where
125        K: IntoPyObject<'py>;
126
127    /// Sets an item value.
128    ///
129    /// This is equivalent to the Python statement `self[key] = value`.
130    fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
131    where
132        K: IntoPyObject<'py>,
133        V: IntoPyObject<'py>;
134
135    /// Deletes an item.
136    ///
137    /// This is equivalent to the Python statement `del self[key]`.
138    fn del_item<K>(&self, key: K) -> PyResult<()>
139    where
140        K: IntoPyObject<'py>;
141
142    /// Returns a list of dict keys.
143    ///
144    /// This is equivalent to the Python expression `list(dict.keys())`.
145    fn keys(&self) -> Bound<'py, PyList>;
146
147    /// Returns a list of dict values.
148    ///
149    /// This is equivalent to the Python expression `list(dict.values())`.
150    fn values(&self) -> Bound<'py, PyList>;
151
152    /// Returns a list of dict items.
153    ///
154    /// This is equivalent to the Python expression `list(dict.items())`.
155    fn items(&self) -> Bound<'py, PyList>;
156
157    /// Returns an iterator of `(key, value)` pairs in this dictionary.
158    ///
159    /// # Panics
160    ///
161    /// If PyO3 detects that the dictionary is mutated during iteration, it will panic.
162    /// It is allowed to modify values as you iterate over the dictionary, but only
163    /// so long as the set of keys does not change.
164    fn iter(&self) -> BoundDictIterator<'py>;
165
166    /// Iterates over the contents of this dictionary while holding a critical section on the dict.
167    /// This is useful when the GIL is disabled and the dictionary is shared between threads.
168    /// It is not guaranteed that the dictionary will not be modified during iteration when the
169    /// closure calls arbitrary Python code that releases the critical section held by the
170    /// iterator. Otherwise, the dictionary will not be modified during iteration.
171    ///
172    /// This method is a small performance optimization over `.iter().try_for_each()` when the
173    /// nightly feature is not enabled because we cannot implement an optimised version of
174    /// `iter().try_fold()` on stable yet. If your iteration is infallible then this method has the
175    /// same performance as `.iter().for_each()`.
176    fn locked_for_each<F>(&self, closure: F) -> PyResult<()>
177    where
178        F: Fn(Bound<'py, PyAny>, Bound<'py, PyAny>) -> PyResult<()>;
179
180    /// Returns `self` cast as a `PyMapping`.
181    fn as_mapping(&self) -> &Bound<'py, PyMapping>;
182
183    /// Returns `self` cast as a `PyMapping`.
184    fn into_mapping(self) -> Bound<'py, PyMapping>;
185
186    /// Update this dictionary with the key/value pairs from another.
187    ///
188    /// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want
189    /// to use `self.update(other.as_mapping())`, note: `PyDict::as_mapping` is a zero-cost conversion.
190    fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
191
192    /// Add key/value pairs from another dictionary to this one only when they do not exist in this.
193    ///
194    /// This is equivalent to the Python expression `self.update({k: v for k, v in other.items() if k not in self})`.
195    /// If `other` is a `PyDict`, you may want to use `self.update_if_missing(other.as_mapping())`,
196    /// note: `PyDict::as_mapping` is a zero-cost conversion.
197    ///
198    /// This method uses [`PyDict_Merge`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Merge) internally,
199    /// so should have the same performance as `update`.
200    fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
201}
202
203impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> {
204    fn copy(&self) -> PyResult<Bound<'py, PyDict>> {
205        unsafe {
206            ffi::PyDict_Copy(self.as_ptr())
207                .assume_owned_or_err(self.py())
208                .downcast_into_unchecked()
209        }
210    }
211
212    fn clear(&self) {
213        unsafe { ffi::PyDict_Clear(self.as_ptr()) }
214    }
215
216    fn len(&self) -> usize {
217        dict_len(self) as usize
218    }
219
220    fn is_empty(&self) -> bool {
221        self.len() == 0
222    }
223
224    fn contains<K>(&self, key: K) -> PyResult<bool>
225    where
226        K: IntoPyObject<'py>,
227    {
228        fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult<bool> {
229            match unsafe { ffi::PyDict_Contains(dict.as_ptr(), key.as_ptr()) } {
230                1 => Ok(true),
231                0 => Ok(false),
232                _ => Err(PyErr::fetch(dict.py())),
233            }
234        }
235
236        let py = self.py();
237        inner(
238            self,
239            key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
240        )
241    }
242
243    fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
244    where
245        K: IntoPyObject<'py>,
246    {
247        fn inner<'py>(
248            dict: &Bound<'py, PyDict>,
249            key: Borrowed<'_, '_, PyAny>,
250        ) -> PyResult<Option<Bound<'py, PyAny>>> {
251            let py = dict.py();
252            let mut result: *mut ffi::PyObject = std::ptr::null_mut();
253            match unsafe {
254                ffi::compat::PyDict_GetItemRef(dict.as_ptr(), key.as_ptr(), &mut result)
255            } {
256                std::os::raw::c_int::MIN..=-1 => Err(PyErr::fetch(py)),
257                0 => Ok(None),
258                1..=std::os::raw::c_int::MAX => {
259                    // Safety: PyDict_GetItemRef positive return value means the result is a valid
260                    // owned reference
261                    Ok(Some(unsafe { result.assume_owned_unchecked(py) }))
262                }
263            }
264        }
265
266        let py = self.py();
267        inner(
268            self,
269            key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
270        )
271    }
272
273    fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
274    where
275        K: IntoPyObject<'py>,
276        V: IntoPyObject<'py>,
277    {
278        fn inner(
279            dict: &Bound<'_, PyDict>,
280            key: Borrowed<'_, '_, PyAny>,
281            value: Borrowed<'_, '_, PyAny>,
282        ) -> PyResult<()> {
283            err::error_on_minusone(dict.py(), unsafe {
284                ffi::PyDict_SetItem(dict.as_ptr(), key.as_ptr(), value.as_ptr())
285            })
286        }
287
288        let py = self.py();
289        inner(
290            self,
291            key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
292            value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
293        )
294    }
295
296    fn del_item<K>(&self, key: K) -> PyResult<()>
297    where
298        K: IntoPyObject<'py>,
299    {
300        fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult<()> {
301            err::error_on_minusone(dict.py(), unsafe {
302                ffi::PyDict_DelItem(dict.as_ptr(), key.as_ptr())
303            })
304        }
305
306        let py = self.py();
307        inner(
308            self,
309            key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
310        )
311    }
312
313    fn keys(&self) -> Bound<'py, PyList> {
314        unsafe {
315            ffi::PyDict_Keys(self.as_ptr())
316                .assume_owned(self.py())
317                .downcast_into_unchecked()
318        }
319    }
320
321    fn values(&self) -> Bound<'py, PyList> {
322        unsafe {
323            ffi::PyDict_Values(self.as_ptr())
324                .assume_owned(self.py())
325                .downcast_into_unchecked()
326        }
327    }
328
329    fn items(&self) -> Bound<'py, PyList> {
330        unsafe {
331            ffi::PyDict_Items(self.as_ptr())
332                .assume_owned(self.py())
333                .downcast_into_unchecked()
334        }
335    }
336
337    fn iter(&self) -> BoundDictIterator<'py> {
338        BoundDictIterator::new(self.clone())
339    }
340
341    fn locked_for_each<F>(&self, f: F) -> PyResult<()>
342    where
343        F: Fn(Bound<'py, PyAny>, Bound<'py, PyAny>) -> PyResult<()>,
344    {
345        #[cfg(feature = "nightly")]
346        {
347            // We don't need a critical section when the nightly feature is enabled because
348            // try_for_each is locked by the implementation of try_fold.
349            self.iter().try_for_each(|(key, value)| f(key, value))
350        }
351
352        #[cfg(not(feature = "nightly"))]
353        {
354            crate::sync::with_critical_section(self, || {
355                self.iter().try_for_each(|(key, value)| f(key, value))
356            })
357        }
358    }
359
360    fn as_mapping(&self) -> &Bound<'py, PyMapping> {
361        unsafe { self.downcast_unchecked() }
362    }
363
364    fn into_mapping(self) -> Bound<'py, PyMapping> {
365        unsafe { self.into_any().downcast_into_unchecked() }
366    }
367
368    fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
369        err::error_on_minusone(self.py(), unsafe {
370            ffi::PyDict_Update(self.as_ptr(), other.as_ptr())
371        })
372    }
373
374    fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
375        err::error_on_minusone(self.py(), unsafe {
376            ffi::PyDict_Merge(self.as_ptr(), other.as_ptr(), 0)
377        })
378    }
379}
380
381impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
382    /// Iterates over the contents of this dictionary without incrementing reference counts.
383    ///
384    /// # Safety
385    /// It must be known that this dictionary will not be modified during iteration,
386    /// for example, when parsing arguments in a keyword arguments dictionary.
387    pub(crate) unsafe fn iter_borrowed(self) -> BorrowedDictIter<'a, 'py> {
388        BorrowedDictIter::new(self)
389    }
390}
391
392fn dict_len(dict: &Bound<'_, PyDict>) -> Py_ssize_t {
393    #[cfg(any(not(Py_3_8), PyPy, GraalPy, Py_LIMITED_API, Py_GIL_DISABLED))]
394    unsafe {
395        ffi::PyDict_Size(dict.as_ptr())
396    }
397
398    #[cfg(all(
399        Py_3_8,
400        not(PyPy),
401        not(GraalPy),
402        not(Py_LIMITED_API),
403        not(Py_GIL_DISABLED)
404    ))]
405    unsafe {
406        (*dict.as_ptr().cast::<ffi::PyDictObject>()).ma_used
407    }
408}
409
410/// PyO3 implementation of an iterator for a Python `dict` object.
411pub struct BoundDictIterator<'py> {
412    dict: Bound<'py, PyDict>,
413    inner: DictIterImpl,
414}
415
416enum DictIterImpl {
417    DictIter {
418        ppos: ffi::Py_ssize_t,
419        di_used: ffi::Py_ssize_t,
420        remaining: ffi::Py_ssize_t,
421    },
422}
423
424impl DictIterImpl {
425    #[deny(unsafe_op_in_unsafe_fn)]
426    #[inline]
427    /// Safety: the dict should be locked with a critical section on the free-threaded build
428    /// and otherwise not shared between threads in code that releases the GIL.
429    unsafe fn next_unchecked<'py>(
430        &mut self,
431        dict: &Bound<'py, PyDict>,
432    ) -> Option<(Bound<'py, PyAny>, Bound<'py, PyAny>)> {
433        match self {
434            Self::DictIter {
435                di_used,
436                remaining,
437                ppos,
438                ..
439            } => {
440                let ma_used = dict_len(dict);
441
442                // These checks are similar to what CPython does.
443                //
444                // If the dimension of the dict changes e.g. key-value pairs are removed
445                // or added during iteration, this will panic next time when `next` is called
446                if *di_used != ma_used {
447                    *di_used = -1;
448                    panic!("dictionary changed size during iteration");
449                };
450
451                // If the dict is changed in such a way that the length remains constant
452                // then this will panic at the end of iteration - similar to this:
453                //
454                // d = {"a":1, "b":2, "c": 3}
455                //
456                // for k, v in d.items():
457                //     d[f"{k}_"] = 4
458                //     del d[k]
459                //     print(k)
460                //
461                if *remaining == -1 {
462                    *di_used = -1;
463                    panic!("dictionary keys changed during iteration");
464                };
465
466                let mut key: *mut ffi::PyObject = std::ptr::null_mut();
467                let mut value: *mut ffi::PyObject = std::ptr::null_mut();
468
469                if unsafe { ffi::PyDict_Next(dict.as_ptr(), ppos, &mut key, &mut value) != 0 } {
470                    *remaining -= 1;
471                    let py = dict.py();
472                    // Safety:
473                    // - PyDict_Next returns borrowed values
474                    // - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
475                    Some((
476                        unsafe { key.assume_borrowed_unchecked(py).to_owned() },
477                        unsafe { value.assume_borrowed_unchecked(py).to_owned() },
478                    ))
479                } else {
480                    None
481                }
482            }
483        }
484    }
485
486    #[cfg(Py_GIL_DISABLED)]
487    #[inline]
488    fn with_critical_section<F, R>(&mut self, dict: &Bound<'_, PyDict>, f: F) -> R
489    where
490        F: FnOnce(&mut Self) -> R,
491    {
492        match self {
493            Self::DictIter { .. } => crate::sync::with_critical_section(dict, || f(self)),
494        }
495    }
496}
497
498impl<'py> Iterator for BoundDictIterator<'py> {
499    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
500
501    #[inline]
502    fn next(&mut self) -> Option<Self::Item> {
503        #[cfg(Py_GIL_DISABLED)]
504        {
505            self.inner
506                .with_critical_section(&self.dict, |inner| unsafe {
507                    inner.next_unchecked(&self.dict)
508                })
509        }
510        #[cfg(not(Py_GIL_DISABLED))]
511        {
512            unsafe { self.inner.next_unchecked(&self.dict) }
513        }
514    }
515
516    #[inline]
517    fn size_hint(&self) -> (usize, Option<usize>) {
518        let len = self.len();
519        (len, Some(len))
520    }
521
522    #[inline]
523    fn count(self) -> usize
524    where
525        Self: Sized,
526    {
527        self.len()
528    }
529
530    #[inline]
531    #[cfg(Py_GIL_DISABLED)]
532    fn fold<B, F>(mut self, init: B, mut f: F) -> B
533    where
534        Self: Sized,
535        F: FnMut(B, Self::Item) -> B,
536    {
537        self.inner.with_critical_section(&self.dict, |inner| {
538            let mut accum = init;
539            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
540                accum = f(accum, x);
541            }
542            accum
543        })
544    }
545
546    #[inline]
547    #[cfg(all(Py_GIL_DISABLED, feature = "nightly"))]
548    fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
549    where
550        Self: Sized,
551        F: FnMut(B, Self::Item) -> R,
552        R: std::ops::Try<Output = B>,
553    {
554        self.inner.with_critical_section(&self.dict, |inner| {
555            let mut accum = init;
556            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
557                accum = f(accum, x)?
558            }
559            R::from_output(accum)
560        })
561    }
562
563    #[inline]
564    #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
565    fn all<F>(&mut self, mut f: F) -> bool
566    where
567        Self: Sized,
568        F: FnMut(Self::Item) -> bool,
569    {
570        self.inner.with_critical_section(&self.dict, |inner| {
571            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
572                if !f(x) {
573                    return false;
574                }
575            }
576            true
577        })
578    }
579
580    #[inline]
581    #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
582    fn any<F>(&mut self, mut f: F) -> bool
583    where
584        Self: Sized,
585        F: FnMut(Self::Item) -> bool,
586    {
587        self.inner.with_critical_section(&self.dict, |inner| {
588            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
589                if f(x) {
590                    return true;
591                }
592            }
593            false
594        })
595    }
596
597    #[inline]
598    #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
599    fn find<P>(&mut self, mut predicate: P) -> Option<Self::Item>
600    where
601        Self: Sized,
602        P: FnMut(&Self::Item) -> bool,
603    {
604        self.inner.with_critical_section(&self.dict, |inner| {
605            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
606                if predicate(&x) {
607                    return Some(x);
608                }
609            }
610            None
611        })
612    }
613
614    #[inline]
615    #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
616    fn find_map<B, F>(&mut self, mut f: F) -> Option<B>
617    where
618        Self: Sized,
619        F: FnMut(Self::Item) -> Option<B>,
620    {
621        self.inner.with_critical_section(&self.dict, |inner| {
622            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
623                if let found @ Some(_) = f(x) {
624                    return found;
625                }
626            }
627            None
628        })
629    }
630
631    #[inline]
632    #[cfg(all(Py_GIL_DISABLED, not(feature = "nightly")))]
633    fn position<P>(&mut self, mut predicate: P) -> Option<usize>
634    where
635        Self: Sized,
636        P: FnMut(Self::Item) -> bool,
637    {
638        self.inner.with_critical_section(&self.dict, |inner| {
639            let mut acc = 0;
640            while let Some(x) = unsafe { inner.next_unchecked(&self.dict) } {
641                if predicate(x) {
642                    return Some(acc);
643                }
644                acc += 1;
645            }
646            None
647        })
648    }
649}
650
651impl ExactSizeIterator for BoundDictIterator<'_> {
652    fn len(&self) -> usize {
653        match self.inner {
654            DictIterImpl::DictIter { remaining, .. } => remaining as usize,
655        }
656    }
657}
658
659impl<'py> BoundDictIterator<'py> {
660    fn new(dict: Bound<'py, PyDict>) -> Self {
661        let remaining = dict_len(&dict);
662
663        Self {
664            dict,
665            inner: DictIterImpl::DictIter {
666                ppos: 0,
667                di_used: remaining,
668                remaining,
669            },
670        }
671    }
672}
673
674impl<'py> IntoIterator for Bound<'py, PyDict> {
675    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
676    type IntoIter = BoundDictIterator<'py>;
677
678    fn into_iter(self) -> Self::IntoIter {
679        BoundDictIterator::new(self)
680    }
681}
682
683impl<'py> IntoIterator for &Bound<'py, PyDict> {
684    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
685    type IntoIter = BoundDictIterator<'py>;
686
687    fn into_iter(self) -> Self::IntoIter {
688        self.iter()
689    }
690}
691
692mod borrowed_iter {
693    use super::*;
694
695    /// Variant of the above which is used to iterate the items of the dictionary
696    /// without incrementing reference counts. This is only safe if it's known
697    /// that the dictionary will not be modified during iteration.
698    pub struct BorrowedDictIter<'a, 'py> {
699        dict: Borrowed<'a, 'py, PyDict>,
700        ppos: ffi::Py_ssize_t,
701        len: ffi::Py_ssize_t,
702    }
703
704    impl<'a, 'py> Iterator for BorrowedDictIter<'a, 'py> {
705        type Item = (Borrowed<'a, 'py, PyAny>, Borrowed<'a, 'py, PyAny>);
706
707        #[inline]
708        fn next(&mut self) -> Option<Self::Item> {
709            let mut key: *mut ffi::PyObject = std::ptr::null_mut();
710            let mut value: *mut ffi::PyObject = std::ptr::null_mut();
711
712            // Safety: self.dict lives sufficiently long that the pointer is not dangling
713            if unsafe { ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) }
714                != 0
715            {
716                let py = self.dict.py();
717                self.len -= 1;
718                // Safety:
719                // - PyDict_Next returns borrowed values
720                // - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
721                Some(unsafe { (key.assume_borrowed(py), value.assume_borrowed(py)) })
722            } else {
723                None
724            }
725        }
726
727        #[inline]
728        fn size_hint(&self) -> (usize, Option<usize>) {
729            let len = self.len();
730            (len, Some(len))
731        }
732
733        #[inline]
734        fn count(self) -> usize
735        where
736            Self: Sized,
737        {
738            self.len()
739        }
740    }
741
742    impl ExactSizeIterator for BorrowedDictIter<'_, '_> {
743        fn len(&self) -> usize {
744            self.len as usize
745        }
746    }
747
748    impl<'a, 'py> BorrowedDictIter<'a, 'py> {
749        pub(super) fn new(dict: Borrowed<'a, 'py, PyDict>) -> Self {
750            let len = dict_len(&dict);
751            BorrowedDictIter { dict, ppos: 0, len }
752        }
753    }
754}
755
756pub(crate) use borrowed_iter::BorrowedDictIter;
757
758/// Conversion trait that allows a sequence of tuples to be converted into `PyDict`
759/// Primary use case for this trait is `call` and `call_method` methods as keywords argument.
760pub trait IntoPyDict<'py>: Sized {
761    /// Converts self into a `PyDict` object pointer. Whether pointer owned or borrowed
762    /// depends on implementation.
763    fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>>;
764}
765
766impl<'py, T, I> IntoPyDict<'py> for I
767where
768    T: PyDictItem<'py>,
769    I: IntoIterator<Item = T>,
770{
771    fn into_py_dict(self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
772        let dict = PyDict::new(py);
773        self.into_iter().try_for_each(|item| {
774            let (key, value) = item.unpack();
775            dict.set_item(key, value)
776        })?;
777        Ok(dict)
778    }
779}
780
781/// Represents a tuple which can be used as a PyDict item.
782trait PyDictItem<'py> {
783    type K: IntoPyObject<'py>;
784    type V: IntoPyObject<'py>;
785    fn unpack(self) -> (Self::K, Self::V);
786}
787
788impl<'py, K, V> PyDictItem<'py> for (K, V)
789where
790    K: IntoPyObject<'py>,
791    V: IntoPyObject<'py>,
792{
793    type K = K;
794    type V = V;
795
796    fn unpack(self) -> (Self::K, Self::V) {
797        (self.0, self.1)
798    }
799}
800
801impl<'a, 'py, K, V> PyDictItem<'py> for &'a (K, V)
802where
803    &'a K: IntoPyObject<'py>,
804    &'a V: IntoPyObject<'py>,
805{
806    type K = &'a K;
807    type V = &'a V;
808
809    fn unpack(self) -> (Self::K, Self::V) {
810        (&self.0, &self.1)
811    }
812}
813
814#[cfg(test)]
815mod tests {
816    use super::*;
817    use crate::types::PyTuple;
818    use std::collections::{BTreeMap, HashMap};
819
820    #[test]
821    fn test_new() {
822        Python::with_gil(|py| {
823            let dict = [(7, 32)].into_py_dict(py).unwrap();
824            assert_eq!(
825                32,
826                dict.get_item(7i32)
827                    .unwrap()
828                    .unwrap()
829                    .extract::<i32>()
830                    .unwrap()
831            );
832            assert!(dict.get_item(8i32).unwrap().is_none());
833            let map: HashMap<i32, i32> = [(7, 32)].iter().cloned().collect();
834            assert_eq!(map, dict.extract().unwrap());
835            let map: BTreeMap<i32, i32> = [(7, 32)].iter().cloned().collect();
836            assert_eq!(map, dict.extract().unwrap());
837        });
838    }
839
840    #[test]
841    #[cfg(not(any(PyPy, GraalPy)))]
842    fn test_from_sequence() {
843        Python::with_gil(|py| {
844            let items = PyList::new(py, vec![("a", 1), ("b", 2)]).unwrap();
845            let dict = PyDict::from_sequence(&items).unwrap();
846            assert_eq!(
847                1,
848                dict.get_item("a")
849                    .unwrap()
850                    .unwrap()
851                    .extract::<i32>()
852                    .unwrap()
853            );
854            assert_eq!(
855                2,
856                dict.get_item("b")
857                    .unwrap()
858                    .unwrap()
859                    .extract::<i32>()
860                    .unwrap()
861            );
862            let map: HashMap<String, i32> =
863                [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
864            assert_eq!(map, dict.extract().unwrap());
865            let map: BTreeMap<String, i32> =
866                [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
867            assert_eq!(map, dict.extract().unwrap());
868        });
869    }
870
871    #[test]
872    #[cfg(not(any(PyPy, GraalPy)))]
873    fn test_from_sequence_err() {
874        Python::with_gil(|py| {
875            let items = PyList::new(py, vec!["a", "b"]).unwrap();
876            assert!(PyDict::from_sequence(&items).is_err());
877        });
878    }
879
880    #[test]
881    fn test_copy() {
882        Python::with_gil(|py| {
883            let dict = [(7, 32)].into_py_dict(py).unwrap();
884
885            let ndict = dict.copy().unwrap();
886            assert_eq!(
887                32,
888                ndict
889                    .get_item(7i32)
890                    .unwrap()
891                    .unwrap()
892                    .extract::<i32>()
893                    .unwrap()
894            );
895            assert!(ndict.get_item(8i32).unwrap().is_none());
896        });
897    }
898
899    #[test]
900    fn test_len() {
901        Python::with_gil(|py| {
902            let mut v = HashMap::<i32, i32>::new();
903            let dict = (&v).into_pyobject(py).unwrap();
904            assert_eq!(0, dict.len());
905            v.insert(7, 32);
906            let dict2 = v.into_pyobject(py).unwrap();
907            assert_eq!(1, dict2.len());
908        });
909    }
910
911    #[test]
912    fn test_contains() {
913        Python::with_gil(|py| {
914            let mut v = HashMap::new();
915            v.insert(7, 32);
916            let dict = v.into_pyobject(py).unwrap();
917            assert!(dict.contains(7i32).unwrap());
918            assert!(!dict.contains(8i32).unwrap());
919        });
920    }
921
922    #[test]
923    fn test_get_item() {
924        Python::with_gil(|py| {
925            let mut v = HashMap::new();
926            v.insert(7, 32);
927            let dict = v.into_pyobject(py).unwrap();
928            assert_eq!(
929                32,
930                dict.get_item(7i32)
931                    .unwrap()
932                    .unwrap()
933                    .extract::<i32>()
934                    .unwrap()
935            );
936            assert!(dict.get_item(8i32).unwrap().is_none());
937        });
938    }
939
940    #[cfg(feature = "macros")]
941    #[test]
942    fn test_get_item_error_path() {
943        use crate::exceptions::PyTypeError;
944
945        #[crate::pyclass(crate = "crate")]
946        struct HashErrors;
947
948        #[crate::pymethods(crate = "crate")]
949        impl HashErrors {
950            #[new]
951            fn new() -> Self {
952                HashErrors {}
953            }
954
955            fn __hash__(&self) -> PyResult<isize> {
956                Err(PyTypeError::new_err("Error from __hash__"))
957            }
958        }
959
960        Python::with_gil(|py| {
961            let class = py.get_type::<HashErrors>();
962            let instance = class.call0().unwrap();
963            let d = PyDict::new(py);
964            match d.get_item(instance) {
965                Ok(_) => {
966                    panic!("this get_item call should always error")
967                }
968                Err(err) => {
969                    assert!(err.is_instance_of::<PyTypeError>(py));
970                    assert_eq!(err.value(py).to_string(), "Error from __hash__")
971                }
972            }
973        })
974    }
975
976    #[test]
977    fn test_set_item() {
978        Python::with_gil(|py| {
979            let mut v = HashMap::new();
980            v.insert(7, 32);
981            let dict = v.into_pyobject(py).unwrap();
982            assert!(dict.set_item(7i32, 42i32).is_ok()); // change
983            assert!(dict.set_item(8i32, 123i32).is_ok()); // insert
984            assert_eq!(
985                42i32,
986                dict.get_item(7i32)
987                    .unwrap()
988                    .unwrap()
989                    .extract::<i32>()
990                    .unwrap()
991            );
992            assert_eq!(
993                123i32,
994                dict.get_item(8i32)
995                    .unwrap()
996                    .unwrap()
997                    .extract::<i32>()
998                    .unwrap()
999            );
1000        });
1001    }
1002
1003    #[test]
1004    fn test_set_item_refcnt() {
1005        Python::with_gil(|py| {
1006            let cnt;
1007            let obj = py.eval(ffi::c_str!("object()"), None, None).unwrap();
1008            {
1009                cnt = obj.get_refcnt();
1010                let _dict = [(10, &obj)].into_py_dict(py);
1011            }
1012            {
1013                assert_eq!(cnt, obj.get_refcnt());
1014            }
1015        });
1016    }
1017
1018    #[test]
1019    fn test_set_item_does_not_update_original_object() {
1020        Python::with_gil(|py| {
1021            let mut v = HashMap::new();
1022            v.insert(7, 32);
1023            let dict = (&v).into_pyobject(py).unwrap();
1024            assert!(dict.set_item(7i32, 42i32).is_ok()); // change
1025            assert!(dict.set_item(8i32, 123i32).is_ok()); // insert
1026            assert_eq!(32i32, v[&7i32]); // not updated!
1027            assert_eq!(None, v.get(&8i32));
1028        });
1029    }
1030
1031    #[test]
1032    fn test_del_item() {
1033        Python::with_gil(|py| {
1034            let mut v = HashMap::new();
1035            v.insert(7, 32);
1036            let dict = v.into_pyobject(py).unwrap();
1037            assert!(dict.del_item(7i32).is_ok());
1038            assert_eq!(0, dict.len());
1039            assert!(dict.get_item(7i32).unwrap().is_none());
1040        });
1041    }
1042
1043    #[test]
1044    fn test_del_item_does_not_update_original_object() {
1045        Python::with_gil(|py| {
1046            let mut v = HashMap::new();
1047            v.insert(7, 32);
1048            let dict = (&v).into_pyobject(py).unwrap();
1049            assert!(dict.del_item(7i32).is_ok()); // change
1050            assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated!
1051        });
1052    }
1053
1054    #[test]
1055    fn test_items() {
1056        Python::with_gil(|py| {
1057            let mut v = HashMap::new();
1058            v.insert(7, 32);
1059            v.insert(8, 42);
1060            v.insert(9, 123);
1061            let dict = v.into_pyobject(py).unwrap();
1062            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1063            let mut key_sum = 0;
1064            let mut value_sum = 0;
1065            for el in dict.items() {
1066                let tuple = el.downcast::<PyTuple>().unwrap();
1067                key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
1068                value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
1069            }
1070            assert_eq!(7 + 8 + 9, key_sum);
1071            assert_eq!(32 + 42 + 123, value_sum);
1072        });
1073    }
1074
1075    #[test]
1076    fn test_keys() {
1077        Python::with_gil(|py| {
1078            let mut v = HashMap::new();
1079            v.insert(7, 32);
1080            v.insert(8, 42);
1081            v.insert(9, 123);
1082            let dict = v.into_pyobject(py).unwrap();
1083            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1084            let mut key_sum = 0;
1085            for el in dict.keys() {
1086                key_sum += el.extract::<i32>().unwrap();
1087            }
1088            assert_eq!(7 + 8 + 9, key_sum);
1089        });
1090    }
1091
1092    #[test]
1093    fn test_values() {
1094        Python::with_gil(|py| {
1095            let mut v = HashMap::new();
1096            v.insert(7, 32);
1097            v.insert(8, 42);
1098            v.insert(9, 123);
1099            let dict = v.into_pyobject(py).unwrap();
1100            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1101            let mut values_sum = 0;
1102            for el in dict.values() {
1103                values_sum += el.extract::<i32>().unwrap();
1104            }
1105            assert_eq!(32 + 42 + 123, values_sum);
1106        });
1107    }
1108
1109    #[test]
1110    fn test_iter() {
1111        Python::with_gil(|py| {
1112            let mut v = HashMap::new();
1113            v.insert(7, 32);
1114            v.insert(8, 42);
1115            v.insert(9, 123);
1116            let dict = v.into_pyobject(py).unwrap();
1117            let mut key_sum = 0;
1118            let mut value_sum = 0;
1119            for (key, value) in dict {
1120                key_sum += key.extract::<i32>().unwrap();
1121                value_sum += value.extract::<i32>().unwrap();
1122            }
1123            assert_eq!(7 + 8 + 9, key_sum);
1124            assert_eq!(32 + 42 + 123, value_sum);
1125        });
1126    }
1127
1128    #[test]
1129    fn test_iter_bound() {
1130        Python::with_gil(|py| {
1131            let mut v = HashMap::new();
1132            v.insert(7, 32);
1133            v.insert(8, 42);
1134            v.insert(9, 123);
1135            let dict = v.into_pyobject(py).unwrap();
1136            let mut key_sum = 0;
1137            let mut value_sum = 0;
1138            for (key, value) in dict {
1139                key_sum += key.extract::<i32>().unwrap();
1140                value_sum += value.extract::<i32>().unwrap();
1141            }
1142            assert_eq!(7 + 8 + 9, key_sum);
1143            assert_eq!(32 + 42 + 123, value_sum);
1144        });
1145    }
1146
1147    #[test]
1148    fn test_iter_value_mutated() {
1149        Python::with_gil(|py| {
1150            let mut v = HashMap::new();
1151            v.insert(7, 32);
1152            v.insert(8, 42);
1153            v.insert(9, 123);
1154
1155            let dict = (&v).into_pyobject(py).unwrap();
1156
1157            for (key, value) in &dict {
1158                dict.set_item(key, value.extract::<i32>().unwrap() + 7)
1159                    .unwrap();
1160            }
1161        });
1162    }
1163
1164    #[test]
1165    #[should_panic]
1166    fn test_iter_key_mutated() {
1167        Python::with_gil(|py| {
1168            let mut v = HashMap::new();
1169            for i in 0..10 {
1170                v.insert(i * 2, i * 2);
1171            }
1172            let dict = v.into_pyobject(py).unwrap();
1173
1174            for (i, (key, value)) in dict.iter().enumerate() {
1175                let key = key.extract::<i32>().unwrap();
1176                let value = value.extract::<i32>().unwrap();
1177
1178                dict.set_item(key + 1, value + 1).unwrap();
1179
1180                if i > 1000 {
1181                    // avoid this test just running out of memory if it fails
1182                    break;
1183                };
1184            }
1185        });
1186    }
1187
1188    #[test]
1189    #[should_panic]
1190    fn test_iter_key_mutated_constant_len() {
1191        Python::with_gil(|py| {
1192            let mut v = HashMap::new();
1193            for i in 0..10 {
1194                v.insert(i * 2, i * 2);
1195            }
1196            let dict = v.into_pyobject(py).unwrap();
1197
1198            for (i, (key, value)) in dict.iter().enumerate() {
1199                let key = key.extract::<i32>().unwrap();
1200                let value = value.extract::<i32>().unwrap();
1201                dict.del_item(key).unwrap();
1202                dict.set_item(key + 1, value + 1).unwrap();
1203
1204                if i > 1000 {
1205                    // avoid this test just running out of memory if it fails
1206                    break;
1207                };
1208            }
1209        });
1210    }
1211
1212    #[test]
1213    fn test_iter_size_hint() {
1214        Python::with_gil(|py| {
1215            let mut v = HashMap::new();
1216            v.insert(7, 32);
1217            v.insert(8, 42);
1218            v.insert(9, 123);
1219            let dict = (&v).into_pyobject(py).unwrap();
1220
1221            let mut iter = dict.iter();
1222            assert_eq!(iter.size_hint(), (v.len(), Some(v.len())));
1223            iter.next();
1224            assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1)));
1225
1226            // Exhaust iterator.
1227            for _ in &mut iter {}
1228
1229            assert_eq!(iter.size_hint(), (0, Some(0)));
1230
1231            assert!(iter.next().is_none());
1232
1233            assert_eq!(iter.size_hint(), (0, Some(0)));
1234        });
1235    }
1236
1237    #[test]
1238    fn test_into_iter() {
1239        Python::with_gil(|py| {
1240            let mut v = HashMap::new();
1241            v.insert(7, 32);
1242            v.insert(8, 42);
1243            v.insert(9, 123);
1244            let dict = v.into_pyobject(py).unwrap();
1245            let mut key_sum = 0;
1246            let mut value_sum = 0;
1247            for (key, value) in dict {
1248                key_sum += key.extract::<i32>().unwrap();
1249                value_sum += value.extract::<i32>().unwrap();
1250            }
1251            assert_eq!(7 + 8 + 9, key_sum);
1252            assert_eq!(32 + 42 + 123, value_sum);
1253        });
1254    }
1255
1256    #[test]
1257    fn test_hashmap_into_dict() {
1258        Python::with_gil(|py| {
1259            let mut map = HashMap::<i32, i32>::new();
1260            map.insert(1, 1);
1261
1262            let py_map = map.into_py_dict(py).unwrap();
1263
1264            assert_eq!(py_map.len(), 1);
1265            assert_eq!(
1266                py_map
1267                    .get_item(1)
1268                    .unwrap()
1269                    .unwrap()
1270                    .extract::<i32>()
1271                    .unwrap(),
1272                1
1273            );
1274        });
1275    }
1276
1277    #[test]
1278    fn test_btreemap_into_dict() {
1279        Python::with_gil(|py| {
1280            let mut map = BTreeMap::<i32, i32>::new();
1281            map.insert(1, 1);
1282
1283            let py_map = map.into_py_dict(py).unwrap();
1284
1285            assert_eq!(py_map.len(), 1);
1286            assert_eq!(
1287                py_map
1288                    .get_item(1)
1289                    .unwrap()
1290                    .unwrap()
1291                    .extract::<i32>()
1292                    .unwrap(),
1293                1
1294            );
1295        });
1296    }
1297
1298    #[test]
1299    fn test_vec_into_dict() {
1300        Python::with_gil(|py| {
1301            let vec = vec![("a", 1), ("b", 2), ("c", 3)];
1302            let py_map = vec.into_py_dict(py).unwrap();
1303
1304            assert_eq!(py_map.len(), 3);
1305            assert_eq!(
1306                py_map
1307                    .get_item("b")
1308                    .unwrap()
1309                    .unwrap()
1310                    .extract::<i32>()
1311                    .unwrap(),
1312                2
1313            );
1314        });
1315    }
1316
1317    #[test]
1318    fn test_slice_into_dict() {
1319        Python::with_gil(|py| {
1320            let arr = [("a", 1), ("b", 2), ("c", 3)];
1321            let py_map = arr.into_py_dict(py).unwrap();
1322
1323            assert_eq!(py_map.len(), 3);
1324            assert_eq!(
1325                py_map
1326                    .get_item("b")
1327                    .unwrap()
1328                    .unwrap()
1329                    .extract::<i32>()
1330                    .unwrap(),
1331                2
1332            );
1333        });
1334    }
1335
1336    #[test]
1337    fn dict_as_mapping() {
1338        Python::with_gil(|py| {
1339            let mut map = HashMap::<i32, i32>::new();
1340            map.insert(1, 1);
1341
1342            let py_map = map.into_py_dict(py).unwrap();
1343
1344            assert_eq!(py_map.as_mapping().len().unwrap(), 1);
1345            assert_eq!(
1346                py_map
1347                    .as_mapping()
1348                    .get_item(1)
1349                    .unwrap()
1350                    .extract::<i32>()
1351                    .unwrap(),
1352                1
1353            );
1354        });
1355    }
1356
1357    #[test]
1358    fn dict_into_mapping() {
1359        Python::with_gil(|py| {
1360            let mut map = HashMap::<i32, i32>::new();
1361            map.insert(1, 1);
1362
1363            let py_map = map.into_py_dict(py).unwrap();
1364
1365            let py_mapping = py_map.into_mapping();
1366            assert_eq!(py_mapping.len().unwrap(), 1);
1367            assert_eq!(py_mapping.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
1368        });
1369    }
1370
1371    #[cfg(not(any(PyPy, GraalPy)))]
1372    fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> {
1373        let mut map = HashMap::<&'static str, i32>::new();
1374        map.insert("a", 1);
1375        map.insert("b", 2);
1376        map.insert("c", 3);
1377        map.into_py_dict(py).unwrap()
1378    }
1379
1380    #[test]
1381    #[cfg(not(any(PyPy, GraalPy)))]
1382    fn dict_keys_view() {
1383        Python::with_gil(|py| {
1384            let dict = abc_dict(py);
1385            let keys = dict.call_method0("keys").unwrap();
1386            assert!(keys.is_instance(&py.get_type::<PyDictKeys>()).unwrap());
1387        })
1388    }
1389
1390    #[test]
1391    #[cfg(not(any(PyPy, GraalPy)))]
1392    fn dict_values_view() {
1393        Python::with_gil(|py| {
1394            let dict = abc_dict(py);
1395            let values = dict.call_method0("values").unwrap();
1396            assert!(values.is_instance(&py.get_type::<PyDictValues>()).unwrap());
1397        })
1398    }
1399
1400    #[test]
1401    #[cfg(not(any(PyPy, GraalPy)))]
1402    fn dict_items_view() {
1403        Python::with_gil(|py| {
1404            let dict = abc_dict(py);
1405            let items = dict.call_method0("items").unwrap();
1406            assert!(items.is_instance(&py.get_type::<PyDictItems>()).unwrap());
1407        })
1408    }
1409
1410    #[test]
1411    fn dict_update() {
1412        Python::with_gil(|py| {
1413            let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1414            let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1415            dict.update(other.as_mapping()).unwrap();
1416            assert_eq!(dict.len(), 4);
1417            assert_eq!(
1418                dict.get_item("a")
1419                    .unwrap()
1420                    .unwrap()
1421                    .extract::<i32>()
1422                    .unwrap(),
1423                1
1424            );
1425            assert_eq!(
1426                dict.get_item("b")
1427                    .unwrap()
1428                    .unwrap()
1429                    .extract::<i32>()
1430                    .unwrap(),
1431                4
1432            );
1433            assert_eq!(
1434                dict.get_item("c")
1435                    .unwrap()
1436                    .unwrap()
1437                    .extract::<i32>()
1438                    .unwrap(),
1439                5
1440            );
1441            assert_eq!(
1442                dict.get_item("d")
1443                    .unwrap()
1444                    .unwrap()
1445                    .extract::<i32>()
1446                    .unwrap(),
1447                6
1448            );
1449
1450            assert_eq!(other.len(), 3);
1451            assert_eq!(
1452                other
1453                    .get_item("b")
1454                    .unwrap()
1455                    .unwrap()
1456                    .extract::<i32>()
1457                    .unwrap(),
1458                4
1459            );
1460            assert_eq!(
1461                other
1462                    .get_item("c")
1463                    .unwrap()
1464                    .unwrap()
1465                    .extract::<i32>()
1466                    .unwrap(),
1467                5
1468            );
1469            assert_eq!(
1470                other
1471                    .get_item("d")
1472                    .unwrap()
1473                    .unwrap()
1474                    .extract::<i32>()
1475                    .unwrap(),
1476                6
1477            );
1478        })
1479    }
1480
1481    #[test]
1482    fn dict_update_if_missing() {
1483        Python::with_gil(|py| {
1484            let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py).unwrap();
1485            let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py).unwrap();
1486            dict.update_if_missing(other.as_mapping()).unwrap();
1487            assert_eq!(dict.len(), 4);
1488            assert_eq!(
1489                dict.get_item("a")
1490                    .unwrap()
1491                    .unwrap()
1492                    .extract::<i32>()
1493                    .unwrap(),
1494                1
1495            );
1496            assert_eq!(
1497                dict.get_item("b")
1498                    .unwrap()
1499                    .unwrap()
1500                    .extract::<i32>()
1501                    .unwrap(),
1502                2
1503            );
1504            assert_eq!(
1505                dict.get_item("c")
1506                    .unwrap()
1507                    .unwrap()
1508                    .extract::<i32>()
1509                    .unwrap(),
1510                3
1511            );
1512            assert_eq!(
1513                dict.get_item("d")
1514                    .unwrap()
1515                    .unwrap()
1516                    .extract::<i32>()
1517                    .unwrap(),
1518                6
1519            );
1520
1521            assert_eq!(other.len(), 3);
1522            assert_eq!(
1523                other
1524                    .get_item("b")
1525                    .unwrap()
1526                    .unwrap()
1527                    .extract::<i32>()
1528                    .unwrap(),
1529                4
1530            );
1531            assert_eq!(
1532                other
1533                    .get_item("c")
1534                    .unwrap()
1535                    .unwrap()
1536                    .extract::<i32>()
1537                    .unwrap(),
1538                5
1539            );
1540            assert_eq!(
1541                other
1542                    .get_item("d")
1543                    .unwrap()
1544                    .unwrap()
1545                    .extract::<i32>()
1546                    .unwrap(),
1547                6
1548            );
1549        })
1550    }
1551
1552    #[test]
1553    fn test_iter_all() {
1554        Python::with_gil(|py| {
1555            let dict = [(1, true), (2, true), (3, true)].into_py_dict(py).unwrap();
1556            assert!(dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1557
1558            let dict = [(1, true), (2, false), (3, true)].into_py_dict(py).unwrap();
1559            assert!(!dict.iter().all(|(_, v)| v.extract::<bool>().unwrap()));
1560        });
1561    }
1562
1563    #[test]
1564    fn test_iter_any() {
1565        Python::with_gil(|py| {
1566            let dict = [(1, true), (2, false), (3, false)]
1567                .into_py_dict(py)
1568                .unwrap();
1569            assert!(dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1570
1571            let dict = [(1, false), (2, false), (3, false)]
1572                .into_py_dict(py)
1573                .unwrap();
1574            assert!(!dict.iter().any(|(_, v)| v.extract::<bool>().unwrap()));
1575        });
1576    }
1577
1578    #[test]
1579    #[allow(clippy::search_is_some)]
1580    fn test_iter_find() {
1581        Python::with_gil(|py| {
1582            let dict = [(1, false), (2, true), (3, false)]
1583                .into_py_dict(py)
1584                .unwrap();
1585
1586            assert_eq!(
1587                Some((2, true)),
1588                dict.iter()
1589                    .find(|(_, v)| v.extract::<bool>().unwrap())
1590                    .map(|(k, v)| (k.extract().unwrap(), v.extract().unwrap()))
1591            );
1592
1593            let dict = [(1, false), (2, false), (3, false)]
1594                .into_py_dict(py)
1595                .unwrap();
1596
1597            assert!(dict
1598                .iter()
1599                .find(|(_, v)| v.extract::<bool>().unwrap())
1600                .is_none());
1601        });
1602    }
1603
1604    #[test]
1605    #[allow(clippy::search_is_some)]
1606    fn test_iter_position() {
1607        Python::with_gil(|py| {
1608            let dict = [(1, false), (2, false), (3, true)]
1609                .into_py_dict(py)
1610                .unwrap();
1611            assert_eq!(
1612                Some(2),
1613                dict.iter().position(|(_, v)| v.extract::<bool>().unwrap())
1614            );
1615
1616            let dict = [(1, false), (2, false), (3, false)]
1617                .into_py_dict(py)
1618                .unwrap();
1619            assert!(dict
1620                .iter()
1621                .position(|(_, v)| v.extract::<bool>().unwrap())
1622                .is_none());
1623        });
1624    }
1625
1626    #[test]
1627    fn test_iter_fold() {
1628        Python::with_gil(|py| {
1629            let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1630            let sum = dict
1631                .iter()
1632                .fold(0, |acc, (_, v)| acc + v.extract::<i32>().unwrap());
1633            assert_eq!(sum, 6);
1634        });
1635    }
1636
1637    #[test]
1638    fn test_iter_try_fold() {
1639        Python::with_gil(|py| {
1640            let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1641            let sum = dict
1642                .iter()
1643                .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1644                .unwrap();
1645            assert_eq!(sum, 6);
1646
1647            let dict = [(1, "foo"), (2, "bar")].into_py_dict(py).unwrap();
1648            assert!(dict
1649                .iter()
1650                .try_fold(0, |acc, (_, v)| PyResult::Ok(acc + v.extract::<i32>()?))
1651                .is_err());
1652        });
1653    }
1654
1655    #[test]
1656    fn test_iter_count() {
1657        Python::with_gil(|py| {
1658            let dict = [(1, 1), (2, 2), (3, 3)].into_py_dict(py).unwrap();
1659            assert_eq!(dict.iter().count(), 3);
1660        })
1661    }
1662}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here