Skip to main content

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