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