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