1 | use super::PyMapping; |
2 | use crate::err::{self, PyErr, PyResult}; |
3 | use crate::ffi::Py_ssize_t; |
4 | use crate::types::{PyAny, PyList}; |
5 | use crate::{ffi, PyObject, Python, ToPyObject}; |
6 | |
7 | /// Represents a Python `dict`. |
8 | #[repr (transparent)] |
9 | pub struct PyDict(PyAny); |
10 | |
11 | pyobject_native_type!( |
12 | PyDict, |
13 | ffi::PyDictObject, |
14 | pyobject_native_static_type_object!(ffi::PyDict_Type), |
15 | #checkfunction=ffi::PyDict_Check |
16 | ); |
17 | |
18 | /// Represents a Python `dict_keys`. |
19 | #[cfg (not(PyPy))] |
20 | #[repr (transparent)] |
21 | pub struct PyDictKeys(PyAny); |
22 | |
23 | #[cfg (not(PyPy))] |
24 | pyobject_native_type_core!( |
25 | PyDictKeys, |
26 | pyobject_native_static_type_object!(ffi::PyDictKeys_Type), |
27 | #checkfunction=ffi::PyDictKeys_Check |
28 | ); |
29 | |
30 | /// Represents a Python `dict_values`. |
31 | #[cfg (not(PyPy))] |
32 | #[repr (transparent)] |
33 | pub struct PyDictValues(PyAny); |
34 | |
35 | #[cfg (not(PyPy))] |
36 | pyobject_native_type_core!( |
37 | PyDictValues, |
38 | pyobject_native_static_type_object!(ffi::PyDictValues_Type), |
39 | #checkfunction=ffi::PyDictValues_Check |
40 | ); |
41 | |
42 | /// Represents a Python `dict_items`. |
43 | #[cfg (not(PyPy))] |
44 | #[repr (transparent)] |
45 | pub struct PyDictItems(PyAny); |
46 | |
47 | #[cfg (not(PyPy))] |
48 | pyobject_native_type_core!( |
49 | PyDictItems, |
50 | pyobject_native_static_type_object!(ffi::PyDictItems_Type), |
51 | #checkfunction=ffi::PyDictItems_Check |
52 | ); |
53 | |
54 | impl PyDict { |
55 | /// Creates a new empty dictionary. |
56 | pub fn new(py: Python<'_>) -> &PyDict { |
57 | unsafe { py.from_owned_ptr::<PyDict>(ffi::PyDict_New()) } |
58 | } |
59 | |
60 | /// Creates a new dictionary from the sequence given. |
61 | /// |
62 | /// The sequence must consist of `(PyObject, PyObject)`. This is |
63 | /// equivalent to `dict([("a", 1), ("b", 2)])`. |
64 | /// |
65 | /// Returns an error on invalid input. In the case of key collisions, |
66 | /// this keeps the last entry seen. |
67 | #[cfg (not(PyPy))] |
68 | pub fn from_sequence(py: Python<'_>, seq: PyObject) -> PyResult<&PyDict> { |
69 | let dict = Self::new(py); |
70 | err::error_on_minusone(py, unsafe { |
71 | ffi::PyDict_MergeFromSeq2(dict.into_ptr(), seq.into_ptr(), 1) |
72 | })?; |
73 | Ok(dict) |
74 | } |
75 | |
76 | /// Returns a new dictionary that contains the same key-value pairs as self. |
77 | /// |
78 | /// This is equivalent to the Python expression `self.copy()`. |
79 | pub fn copy(&self) -> PyResult<&PyDict> { |
80 | unsafe { |
81 | self.py() |
82 | .from_owned_ptr_or_err::<PyDict>(ffi::PyDict_Copy(self.as_ptr())) |
83 | } |
84 | } |
85 | |
86 | /// Empties an existing dictionary of all key-value pairs. |
87 | pub fn clear(&self) { |
88 | unsafe { ffi::PyDict_Clear(self.as_ptr()) } |
89 | } |
90 | |
91 | /// Return the number of items in the dictionary. |
92 | /// |
93 | /// This is equivalent to the Python expression `len(self)`. |
94 | pub fn len(&self) -> usize { |
95 | self._len() as usize |
96 | } |
97 | |
98 | fn _len(&self) -> Py_ssize_t { |
99 | #[cfg (any(not(Py_3_8), PyPy, Py_LIMITED_API))] |
100 | unsafe { |
101 | ffi::PyDict_Size(self.as_ptr()) |
102 | } |
103 | |
104 | #[cfg (all(Py_3_8, not(PyPy), not(Py_LIMITED_API)))] |
105 | unsafe { |
106 | (*self.as_ptr().cast::<ffi::PyDictObject>()).ma_used |
107 | } |
108 | } |
109 | |
110 | /// Checks if the dict is empty, i.e. `len(self) == 0`. |
111 | pub fn is_empty(&self) -> bool { |
112 | self.len() == 0 |
113 | } |
114 | |
115 | /// Determines if the dictionary contains the specified key. |
116 | /// |
117 | /// This is equivalent to the Python expression `key in self`. |
118 | pub fn contains<K>(&self, key: K) -> PyResult<bool> |
119 | where |
120 | K: ToPyObject, |
121 | { |
122 | fn inner(dict: &PyDict, key: PyObject) -> PyResult<bool> { |
123 | match unsafe { ffi::PyDict_Contains(dict.as_ptr(), key.as_ptr()) } { |
124 | 1 => Ok(true), |
125 | 0 => Ok(false), |
126 | _ => Err(PyErr::fetch(dict.py())), |
127 | } |
128 | } |
129 | |
130 | inner(self, key.to_object(self.py())) |
131 | } |
132 | |
133 | /// Gets an item from the dictionary. |
134 | /// |
135 | /// Returns `Ok(None)` if the item is not present. To get a `KeyError` for |
136 | /// non-existing keys, use [`PyAny::get_item`]. |
137 | /// |
138 | /// Returns `Err(PyErr)` if Python magic methods `__hash__` or `__eq__` used in dictionary |
139 | /// lookup raise an exception, for example if the key `K` is not hashable. Usually it is |
140 | /// best to bubble this error up to the caller using the `?` operator. |
141 | /// |
142 | /// # Examples |
143 | /// |
144 | /// The following example calls `get_item` for the dictionary `{"a": 1}` with various |
145 | /// keys. |
146 | /// - `get_item("a")` returns `Ok(Some(...))`, with the `PyAny` being a reference to the Python |
147 | /// int `1`. |
148 | /// - `get_item("b")` returns `Ok(None)`, because "b" is not in the dictionary. |
149 | /// - `get_item(dict)` returns an `Err(PyErr)`. The error will be a `TypeError` because a dict is not |
150 | /// hashable. |
151 | /// |
152 | /// ```rust |
153 | /// use pyo3::prelude::*; |
154 | /// use pyo3::types::{PyDict, IntoPyDict}; |
155 | /// use pyo3::exceptions::{PyTypeError, PyKeyError}; |
156 | /// |
157 | /// # fn main() { |
158 | /// # let _ = |
159 | /// Python::with_gil(|py| -> PyResult<()> { |
160 | /// let dict: &PyDict = [("a" , 1)].into_py_dict(py); |
161 | /// // `a` is in the dictionary, with value 1 |
162 | /// assert!(dict.get_item("a" )?.map_or(Ok(false), |x| x.eq(1))?); |
163 | /// // `b` is not in the dictionary |
164 | /// assert!(dict.get_item("b" )?.is_none()); |
165 | /// // `dict` is not hashable, so this returns an error |
166 | /// assert!(dict.get_item(dict).unwrap_err().is_instance_of::<PyTypeError>(py)); |
167 | /// |
168 | /// // `PyAny::get_item("b")` will raise a `KeyError` instead of returning `None` |
169 | /// let any: &PyAny = dict.as_ref(); |
170 | /// assert!(any.get_item("b" ).unwrap_err().is_instance_of::<PyKeyError>(py)); |
171 | /// Ok(()) |
172 | /// }); |
173 | /// # } |
174 | /// ``` |
175 | pub fn get_item<K>(&self, key: K) -> PyResult<Option<&PyAny>> |
176 | where |
177 | K: ToPyObject, |
178 | { |
179 | fn inner(dict: &PyDict, key: PyObject) -> PyResult<Option<&PyAny>> { |
180 | let py = dict.py(); |
181 | // PyDict_GetItemWithError returns a borrowed ptr, must make it owned for safety (see #890). |
182 | // PyObject::from_borrowed_ptr_or_opt will take ownership in this way. |
183 | unsafe { |
184 | PyObject::from_borrowed_ptr_or_opt( |
185 | py, |
186 | ffi::PyDict_GetItemWithError(dict.as_ptr(), key.as_ptr()), |
187 | ) |
188 | } |
189 | .map(|pyobject| Ok(pyobject.into_ref(py))) |
190 | .or_else(|| PyErr::take(py).map(Err)) |
191 | .transpose() |
192 | } |
193 | |
194 | inner(self, key.to_object(self.py())) |
195 | } |
196 | |
197 | /// Deprecated version of `get_item`. |
198 | #[deprecated ( |
199 | since = "0.20.0" , |
200 | note = "this is now equivalent to `PyDict::get_item`" |
201 | )] |
202 | #[inline ] |
203 | pub fn get_item_with_error<K>(&self, key: K) -> PyResult<Option<&PyAny>> |
204 | where |
205 | K: ToPyObject, |
206 | { |
207 | self.get_item(key) |
208 | } |
209 | |
210 | /// Sets an item value. |
211 | /// |
212 | /// This is equivalent to the Python statement `self[key] = value`. |
213 | pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()> |
214 | where |
215 | K: ToPyObject, |
216 | V: ToPyObject, |
217 | { |
218 | fn inner(dict: &PyDict, key: PyObject, value: PyObject) -> PyResult<()> { |
219 | err::error_on_minusone(dict.py(), unsafe { |
220 | ffi::PyDict_SetItem(dict.as_ptr(), key.as_ptr(), value.as_ptr()) |
221 | }) |
222 | } |
223 | |
224 | let py = self.py(); |
225 | inner(self, key.to_object(py), value.to_object(py)) |
226 | } |
227 | |
228 | /// Deletes an item. |
229 | /// |
230 | /// This is equivalent to the Python statement `del self[key]`. |
231 | pub fn del_item<K>(&self, key: K) -> PyResult<()> |
232 | where |
233 | K: ToPyObject, |
234 | { |
235 | fn inner(dict: &PyDict, key: PyObject) -> PyResult<()> { |
236 | err::error_on_minusone(dict.py(), unsafe { |
237 | ffi::PyDict_DelItem(dict.as_ptr(), key.as_ptr()) |
238 | }) |
239 | } |
240 | |
241 | inner(self, key.to_object(self.py())) |
242 | } |
243 | |
244 | /// Returns a list of dict keys. |
245 | /// |
246 | /// This is equivalent to the Python expression `list(dict.keys())`. |
247 | pub fn keys(&self) -> &PyList { |
248 | unsafe { |
249 | self.py() |
250 | .from_owned_ptr::<PyList>(ffi::PyDict_Keys(self.as_ptr())) |
251 | } |
252 | } |
253 | |
254 | /// Returns a list of dict values. |
255 | /// |
256 | /// This is equivalent to the Python expression `list(dict.values())`. |
257 | pub fn values(&self) -> &PyList { |
258 | unsafe { |
259 | self.py() |
260 | .from_owned_ptr::<PyList>(ffi::PyDict_Values(self.as_ptr())) |
261 | } |
262 | } |
263 | |
264 | /// Returns a list of dict items. |
265 | /// |
266 | /// This is equivalent to the Python expression `list(dict.items())`. |
267 | pub fn items(&self) -> &PyList { |
268 | unsafe { |
269 | self.py() |
270 | .from_owned_ptr::<PyList>(ffi::PyDict_Items(self.as_ptr())) |
271 | } |
272 | } |
273 | |
274 | /// Returns an iterator of `(key, value)` pairs in this dictionary. |
275 | /// |
276 | /// # Panics |
277 | /// |
278 | /// If PyO3 detects that the dictionary is mutated during iteration, it will panic. |
279 | /// It is allowed to modify values as you iterate over the dictionary, but only |
280 | /// so long as the set of keys does not change. |
281 | pub fn iter(&self) -> PyDictIterator<'_> { |
282 | IntoIterator::into_iter(self) |
283 | } |
284 | |
285 | /// Returns `self` cast as a `PyMapping`. |
286 | pub fn as_mapping(&self) -> &PyMapping { |
287 | unsafe { self.downcast_unchecked() } |
288 | } |
289 | |
290 | /// Update this dictionary with the key/value pairs from another. |
291 | /// |
292 | /// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want |
293 | /// to use `self.update(other.as_mapping())`, note: `PyDict::as_mapping` is a zero-cost conversion. |
294 | pub fn update(&self, other: &PyMapping) -> PyResult<()> { |
295 | let py = self.py(); |
296 | err::error_on_minusone(py, unsafe { |
297 | ffi::PyDict_Update(self.as_ptr(), other.as_ptr()) |
298 | }) |
299 | } |
300 | |
301 | /// Add key/value pairs from another dictionary to this one only when they do not exist in this. |
302 | /// |
303 | /// This is equivalent to the Python expression `self.update({k: v for k, v in other.items() if k not in self})`. |
304 | /// If `other` is a `PyDict`, you may want to use `self.update_if_missing(other.as_mapping())`, |
305 | /// note: `PyDict::as_mapping` is a zero-cost conversion. |
306 | /// |
307 | /// This method uses [`PyDict_Merge`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Merge) internally, |
308 | /// so should have the same performance as `update`. |
309 | pub fn update_if_missing(&self, other: &PyMapping) -> PyResult<()> { |
310 | let py = self.py(); |
311 | err::error_on_minusone(py, unsafe { |
312 | ffi::PyDict_Merge(self.as_ptr(), other.as_ptr(), 0) |
313 | }) |
314 | } |
315 | } |
316 | |
317 | /// PyO3 implementation of an iterator for a Python `dict` object. |
318 | pub struct PyDictIterator<'py> { |
319 | dict: &'py PyDict, |
320 | ppos: ffi::Py_ssize_t, |
321 | di_used: ffi::Py_ssize_t, |
322 | len: ffi::Py_ssize_t, |
323 | } |
324 | |
325 | impl<'py> Iterator for PyDictIterator<'py> { |
326 | type Item = (&'py PyAny, &'py PyAny); |
327 | |
328 | #[inline ] |
329 | fn next(&mut self) -> Option<Self::Item> { |
330 | let ma_used = self.dict._len(); |
331 | |
332 | // These checks are similar to what CPython does. |
333 | // |
334 | // If the dimension of the dict changes e.g. key-value pairs are removed |
335 | // or added during iteration, this will panic next time when `next` is called |
336 | if self.di_used != ma_used { |
337 | self.di_used = -1; |
338 | panic!("dictionary changed size during iteration" ); |
339 | }; |
340 | |
341 | // If the dict is changed in such a way that the length remains constant |
342 | // then this will panic at the end of iteration - similar to this: |
343 | // |
344 | // d = {"a":1, "b":2, "c": 3} |
345 | // |
346 | // for k, v in d.items(): |
347 | // d[f"{k}_"] = 4 |
348 | // del d[k] |
349 | // print(k) |
350 | // |
351 | if self.len == -1 { |
352 | self.di_used = -1; |
353 | panic!("dictionary keys changed during iteration" ); |
354 | }; |
355 | |
356 | let ret = unsafe { self.next_unchecked() }; |
357 | if ret.is_some() { |
358 | self.len -= 1 |
359 | } |
360 | ret |
361 | } |
362 | |
363 | #[inline ] |
364 | fn size_hint(&self) -> (usize, Option<usize>) { |
365 | let len = self.len(); |
366 | (len, Some(len)) |
367 | } |
368 | } |
369 | |
370 | impl<'py> ExactSizeIterator for PyDictIterator<'py> { |
371 | fn len(&self) -> usize { |
372 | self.len as usize |
373 | } |
374 | } |
375 | |
376 | impl<'a> std::iter::IntoIterator for &'a PyDict { |
377 | type Item = (&'a PyAny, &'a PyAny); |
378 | type IntoIter = PyDictIterator<'a>; |
379 | |
380 | fn into_iter(self) -> Self::IntoIter { |
381 | PyDictIterator { |
382 | dict: self, |
383 | ppos: 0, |
384 | di_used: self._len(), |
385 | len: self._len(), |
386 | } |
387 | } |
388 | } |
389 | |
390 | impl<'py> PyDictIterator<'py> { |
391 | /// Advances the iterator without checking for concurrent modification. |
392 | /// |
393 | /// See [`PyDict_Next`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Next) |
394 | /// for more information. |
395 | unsafe fn next_unchecked(&mut self) -> Option<(&'py PyAny, &'py PyAny)> { |
396 | let mut key: *mut ffi::PyObject = std::ptr::null_mut(); |
397 | let mut value: *mut ffi::PyObject = std::ptr::null_mut(); |
398 | |
399 | if ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) != 0 { |
400 | let py: Python<'_> = self.dict.py(); |
401 | // PyDict_Next returns borrowed values; for safety must make them owned (see #890) |
402 | Some(( |
403 | py.from_owned_ptr(ffi::_Py_NewRef(obj:key)), |
404 | py.from_owned_ptr(ffi::_Py_NewRef(obj:value)), |
405 | )) |
406 | } else { |
407 | None |
408 | } |
409 | } |
410 | } |
411 | |
412 | /// Conversion trait that allows a sequence of tuples to be converted into `PyDict` |
413 | /// Primary use case for this trait is `call` and `call_method` methods as keywords argument. |
414 | pub trait IntoPyDict { |
415 | /// Converts self into a `PyDict` object pointer. Whether pointer owned or borrowed |
416 | /// depends on implementation. |
417 | fn into_py_dict(self, py: Python<'_>) -> &PyDict; |
418 | } |
419 | |
420 | impl<T, I> IntoPyDict for I |
421 | where |
422 | T: PyDictItem, |
423 | I: IntoIterator<Item = T>, |
424 | { |
425 | fn into_py_dict(self, py: Python<'_>) -> &PyDict { |
426 | let dict: &PyDict = PyDict::new(py); |
427 | for item: T in self { |
428 | dict.set_item(item.key(), item.value()) |
429 | .expect(msg:"Failed to set_item on dict" ); |
430 | } |
431 | dict |
432 | } |
433 | } |
434 | |
435 | /// Represents a tuple which can be used as a PyDict item. |
436 | pub trait PyDictItem { |
437 | type K: ToPyObject; |
438 | type V: ToPyObject; |
439 | fn key(&self) -> &Self::K; |
440 | fn value(&self) -> &Self::V; |
441 | } |
442 | |
443 | impl<K, V> PyDictItem for (K, V) |
444 | where |
445 | K: ToPyObject, |
446 | V: ToPyObject, |
447 | { |
448 | type K = K; |
449 | type V = V; |
450 | fn key(&self) -> &Self::K { |
451 | &self.0 |
452 | } |
453 | fn value(&self) -> &Self::V { |
454 | &self.1 |
455 | } |
456 | } |
457 | |
458 | impl<K, V> PyDictItem for &(K, V) |
459 | where |
460 | K: ToPyObject, |
461 | V: ToPyObject, |
462 | { |
463 | type K = K; |
464 | type V = V; |
465 | fn key(&self) -> &Self::K { |
466 | &self.0 |
467 | } |
468 | fn value(&self) -> &Self::V { |
469 | &self.1 |
470 | } |
471 | } |
472 | |
473 | #[cfg (test)] |
474 | mod tests { |
475 | use super::*; |
476 | #[cfg (not(PyPy))] |
477 | use crate::exceptions; |
478 | #[cfg (not(PyPy))] |
479 | use crate::types::PyList; |
480 | use crate::{types::PyTuple, Python, ToPyObject}; |
481 | use std::collections::{BTreeMap, HashMap}; |
482 | |
483 | #[test ] |
484 | fn test_new() { |
485 | Python::with_gil(|py| { |
486 | let dict = [(7, 32)].into_py_dict(py); |
487 | assert_eq!( |
488 | 32, |
489 | dict.get_item(7i32) |
490 | .unwrap() |
491 | .unwrap() |
492 | .extract::<i32>() |
493 | .unwrap() |
494 | ); |
495 | assert!(dict.get_item(8i32).unwrap().is_none()); |
496 | let map: HashMap<i32, i32> = [(7, 32)].iter().cloned().collect(); |
497 | assert_eq!(map, dict.extract().unwrap()); |
498 | let map: BTreeMap<i32, i32> = [(7, 32)].iter().cloned().collect(); |
499 | assert_eq!(map, dict.extract().unwrap()); |
500 | }); |
501 | } |
502 | |
503 | #[test ] |
504 | #[cfg (not(PyPy))] |
505 | fn test_from_sequence() { |
506 | Python::with_gil(|py| { |
507 | let items = PyList::new(py, &vec![("a" , 1), ("b" , 2)]); |
508 | let dict = PyDict::from_sequence(py, items.to_object(py)).unwrap(); |
509 | assert_eq!( |
510 | 1, |
511 | dict.get_item("a" ) |
512 | .unwrap() |
513 | .unwrap() |
514 | .extract::<i32>() |
515 | .unwrap() |
516 | ); |
517 | assert_eq!( |
518 | 2, |
519 | dict.get_item("b" ) |
520 | .unwrap() |
521 | .unwrap() |
522 | .extract::<i32>() |
523 | .unwrap() |
524 | ); |
525 | let map: HashMap<&str, i32> = [("a" , 1), ("b" , 2)].iter().cloned().collect(); |
526 | assert_eq!(map, dict.extract().unwrap()); |
527 | let map: BTreeMap<&str, i32> = [("a" , 1), ("b" , 2)].iter().cloned().collect(); |
528 | assert_eq!(map, dict.extract().unwrap()); |
529 | }); |
530 | } |
531 | |
532 | #[test ] |
533 | #[cfg (not(PyPy))] |
534 | fn test_from_sequence_err() { |
535 | Python::with_gil(|py| { |
536 | let items = PyList::new(py, &vec!["a" , "b" ]); |
537 | assert!(PyDict::from_sequence(py, items.to_object(py)).is_err()); |
538 | }); |
539 | } |
540 | |
541 | #[test ] |
542 | fn test_copy() { |
543 | Python::with_gil(|py| { |
544 | let dict = [(7, 32)].into_py_dict(py); |
545 | |
546 | let ndict = dict.copy().unwrap(); |
547 | assert_eq!( |
548 | 32, |
549 | ndict |
550 | .get_item(7i32) |
551 | .unwrap() |
552 | .unwrap() |
553 | .extract::<i32>() |
554 | .unwrap() |
555 | ); |
556 | assert!(ndict.get_item(8i32).unwrap().is_none()); |
557 | }); |
558 | } |
559 | |
560 | #[test ] |
561 | fn test_len() { |
562 | Python::with_gil(|py| { |
563 | let mut v = HashMap::new(); |
564 | let ob = v.to_object(py); |
565 | let dict: &PyDict = ob.downcast(py).unwrap(); |
566 | assert_eq!(0, dict.len()); |
567 | v.insert(7, 32); |
568 | let ob = v.to_object(py); |
569 | let dict2: &PyDict = ob.downcast(py).unwrap(); |
570 | assert_eq!(1, dict2.len()); |
571 | }); |
572 | } |
573 | |
574 | #[test ] |
575 | fn test_contains() { |
576 | Python::with_gil(|py| { |
577 | let mut v = HashMap::new(); |
578 | v.insert(7, 32); |
579 | let ob = v.to_object(py); |
580 | let dict: &PyDict = ob.downcast(py).unwrap(); |
581 | assert!(dict.contains(7i32).unwrap()); |
582 | assert!(!dict.contains(8i32).unwrap()); |
583 | }); |
584 | } |
585 | |
586 | #[test ] |
587 | fn test_get_item() { |
588 | Python::with_gil(|py| { |
589 | let mut v = HashMap::new(); |
590 | v.insert(7, 32); |
591 | let ob = v.to_object(py); |
592 | let dict: &PyDict = ob.downcast(py).unwrap(); |
593 | assert_eq!( |
594 | 32, |
595 | dict.get_item(7i32) |
596 | .unwrap() |
597 | .unwrap() |
598 | .extract::<i32>() |
599 | .unwrap() |
600 | ); |
601 | assert!(dict.get_item(8i32).unwrap().is_none()); |
602 | }); |
603 | } |
604 | |
605 | #[test ] |
606 | #[allow (deprecated)] |
607 | #[cfg (not(PyPy))] |
608 | fn test_get_item_with_error() { |
609 | Python::with_gil(|py| { |
610 | let mut v = HashMap::new(); |
611 | v.insert(7, 32); |
612 | let ob = v.to_object(py); |
613 | let dict: &PyDict = ob.downcast(py).unwrap(); |
614 | assert_eq!( |
615 | 32, |
616 | dict.get_item_with_error(7i32) |
617 | .unwrap() |
618 | .unwrap() |
619 | .extract::<i32>() |
620 | .unwrap() |
621 | ); |
622 | assert!(dict.get_item_with_error(8i32).unwrap().is_none()); |
623 | assert!(dict |
624 | .get_item_with_error(dict) |
625 | .unwrap_err() |
626 | .is_instance_of::<exceptions::PyTypeError>(py)); |
627 | }); |
628 | } |
629 | |
630 | #[test ] |
631 | fn test_set_item() { |
632 | Python::with_gil(|py| { |
633 | let mut v = HashMap::new(); |
634 | v.insert(7, 32); |
635 | let ob = v.to_object(py); |
636 | let dict: &PyDict = ob.downcast(py).unwrap(); |
637 | assert!(dict.set_item(7i32, 42i32).is_ok()); // change |
638 | assert!(dict.set_item(8i32, 123i32).is_ok()); // insert |
639 | assert_eq!( |
640 | 42i32, |
641 | dict.get_item(7i32) |
642 | .unwrap() |
643 | .unwrap() |
644 | .extract::<i32>() |
645 | .unwrap() |
646 | ); |
647 | assert_eq!( |
648 | 123i32, |
649 | dict.get_item(8i32) |
650 | .unwrap() |
651 | .unwrap() |
652 | .extract::<i32>() |
653 | .unwrap() |
654 | ); |
655 | }); |
656 | } |
657 | |
658 | #[test ] |
659 | fn test_set_item_refcnt() { |
660 | Python::with_gil(|py| { |
661 | let cnt; |
662 | let obj = py.eval("object()" , None, None).unwrap(); |
663 | { |
664 | let _pool = unsafe { crate::GILPool::new() }; |
665 | cnt = obj.get_refcnt(); |
666 | let _dict = [(10, obj)].into_py_dict(py); |
667 | } |
668 | { |
669 | assert_eq!(cnt, obj.get_refcnt()); |
670 | } |
671 | }); |
672 | } |
673 | |
674 | #[test ] |
675 | fn test_set_item_does_not_update_original_object() { |
676 | Python::with_gil(|py| { |
677 | let mut v = HashMap::new(); |
678 | v.insert(7, 32); |
679 | let ob = v.to_object(py); |
680 | let dict: &PyDict = ob.downcast(py).unwrap(); |
681 | assert!(dict.set_item(7i32, 42i32).is_ok()); // change |
682 | assert!(dict.set_item(8i32, 123i32).is_ok()); // insert |
683 | assert_eq!(32i32, v[&7i32]); // not updated! |
684 | assert_eq!(None, v.get(&8i32)); |
685 | }); |
686 | } |
687 | |
688 | #[test ] |
689 | fn test_del_item() { |
690 | Python::with_gil(|py| { |
691 | let mut v = HashMap::new(); |
692 | v.insert(7, 32); |
693 | let ob = v.to_object(py); |
694 | let dict: &PyDict = ob.downcast(py).unwrap(); |
695 | assert!(dict.del_item(7i32).is_ok()); |
696 | assert_eq!(0, dict.len()); |
697 | assert!(dict.get_item(7i32).unwrap().is_none()); |
698 | }); |
699 | } |
700 | |
701 | #[test ] |
702 | fn test_del_item_does_not_update_original_object() { |
703 | Python::with_gil(|py| { |
704 | let mut v = HashMap::new(); |
705 | v.insert(7, 32); |
706 | let ob = v.to_object(py); |
707 | let dict: &PyDict = ob.downcast(py).unwrap(); |
708 | assert!(dict.del_item(7i32).is_ok()); // change |
709 | assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated! |
710 | }); |
711 | } |
712 | |
713 | #[test ] |
714 | fn test_items() { |
715 | Python::with_gil(|py| { |
716 | let mut v = HashMap::new(); |
717 | v.insert(7, 32); |
718 | v.insert(8, 42); |
719 | v.insert(9, 123); |
720 | let ob = v.to_object(py); |
721 | let dict: &PyDict = ob.downcast(py).unwrap(); |
722 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
723 | let mut key_sum = 0; |
724 | let mut value_sum = 0; |
725 | for el in dict.items() { |
726 | let tuple = el.downcast::<PyTuple>().unwrap(); |
727 | key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap(); |
728 | value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap(); |
729 | } |
730 | assert_eq!(7 + 8 + 9, key_sum); |
731 | assert_eq!(32 + 42 + 123, value_sum); |
732 | }); |
733 | } |
734 | |
735 | #[test ] |
736 | fn test_keys() { |
737 | Python::with_gil(|py| { |
738 | let mut v = HashMap::new(); |
739 | v.insert(7, 32); |
740 | v.insert(8, 42); |
741 | v.insert(9, 123); |
742 | let ob = v.to_object(py); |
743 | let dict: &PyDict = ob.downcast(py).unwrap(); |
744 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
745 | let mut key_sum = 0; |
746 | for el in dict.keys() { |
747 | key_sum += el.extract::<i32>().unwrap(); |
748 | } |
749 | assert_eq!(7 + 8 + 9, key_sum); |
750 | }); |
751 | } |
752 | |
753 | #[test ] |
754 | fn test_values() { |
755 | Python::with_gil(|py| { |
756 | let mut v = HashMap::new(); |
757 | v.insert(7, 32); |
758 | v.insert(8, 42); |
759 | v.insert(9, 123); |
760 | let ob = v.to_object(py); |
761 | let dict: &PyDict = ob.downcast(py).unwrap(); |
762 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
763 | let mut values_sum = 0; |
764 | for el in dict.values() { |
765 | values_sum += el.extract::<i32>().unwrap(); |
766 | } |
767 | assert_eq!(32 + 42 + 123, values_sum); |
768 | }); |
769 | } |
770 | |
771 | #[test ] |
772 | fn test_iter() { |
773 | Python::with_gil(|py| { |
774 | let mut v = HashMap::new(); |
775 | v.insert(7, 32); |
776 | v.insert(8, 42); |
777 | v.insert(9, 123); |
778 | let ob = v.to_object(py); |
779 | let dict: &PyDict = ob.downcast(py).unwrap(); |
780 | let mut key_sum = 0; |
781 | let mut value_sum = 0; |
782 | for (key, value) in dict { |
783 | key_sum += key.extract::<i32>().unwrap(); |
784 | value_sum += value.extract::<i32>().unwrap(); |
785 | } |
786 | assert_eq!(7 + 8 + 9, key_sum); |
787 | assert_eq!(32 + 42 + 123, value_sum); |
788 | }); |
789 | } |
790 | |
791 | #[test ] |
792 | fn test_iter_value_mutated() { |
793 | Python::with_gil(|py| { |
794 | let mut v = HashMap::new(); |
795 | v.insert(7, 32); |
796 | v.insert(8, 42); |
797 | v.insert(9, 123); |
798 | |
799 | let ob = v.to_object(py); |
800 | let dict: &PyDict = ob.downcast(py).unwrap(); |
801 | |
802 | for (key, value) in dict { |
803 | dict.set_item(key, value.extract::<i32>().unwrap() + 7) |
804 | .unwrap(); |
805 | } |
806 | }); |
807 | } |
808 | |
809 | #[test ] |
810 | #[should_panic ] |
811 | fn test_iter_key_mutated() { |
812 | Python::with_gil(|py| { |
813 | let mut v = HashMap::new(); |
814 | for i in 0..10 { |
815 | v.insert(i * 2, i * 2); |
816 | } |
817 | let ob = v.to_object(py); |
818 | let dict: &PyDict = ob.downcast(py).unwrap(); |
819 | |
820 | for (i, (key, value)) in dict.iter().enumerate() { |
821 | let key = key.extract::<i32>().unwrap(); |
822 | let value = value.extract::<i32>().unwrap(); |
823 | |
824 | dict.set_item(key + 1, value + 1).unwrap(); |
825 | |
826 | if i > 1000 { |
827 | // avoid this test just running out of memory if it fails |
828 | break; |
829 | }; |
830 | } |
831 | }); |
832 | } |
833 | |
834 | #[test ] |
835 | #[should_panic ] |
836 | fn test_iter_key_mutated_constant_len() { |
837 | Python::with_gil(|py| { |
838 | let mut v = HashMap::new(); |
839 | for i in 0..10 { |
840 | v.insert(i * 2, i * 2); |
841 | } |
842 | let ob = v.to_object(py); |
843 | let dict: &PyDict = ob.downcast(py).unwrap(); |
844 | |
845 | for (i, (key, value)) in dict.iter().enumerate() { |
846 | let key = key.extract::<i32>().unwrap(); |
847 | let value = value.extract::<i32>().unwrap(); |
848 | dict.del_item(key).unwrap(); |
849 | dict.set_item(key + 1, value + 1).unwrap(); |
850 | |
851 | if i > 1000 { |
852 | // avoid this test just running out of memory if it fails |
853 | break; |
854 | }; |
855 | } |
856 | }); |
857 | } |
858 | |
859 | #[test ] |
860 | fn test_iter_size_hint() { |
861 | Python::with_gil(|py| { |
862 | let mut v = HashMap::new(); |
863 | v.insert(7, 32); |
864 | v.insert(8, 42); |
865 | v.insert(9, 123); |
866 | let ob = v.to_object(py); |
867 | let dict: &PyDict = ob.downcast(py).unwrap(); |
868 | |
869 | let mut iter = dict.iter(); |
870 | assert_eq!(iter.size_hint(), (v.len(), Some(v.len()))); |
871 | iter.next(); |
872 | assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1))); |
873 | |
874 | // Exhaust iterator. |
875 | for _ in &mut iter {} |
876 | |
877 | assert_eq!(iter.size_hint(), (0, Some(0))); |
878 | |
879 | assert!(iter.next().is_none()); |
880 | |
881 | assert_eq!(iter.size_hint(), (0, Some(0))); |
882 | }); |
883 | } |
884 | |
885 | #[test ] |
886 | fn test_into_iter() { |
887 | Python::with_gil(|py| { |
888 | let mut v = HashMap::new(); |
889 | v.insert(7, 32); |
890 | v.insert(8, 42); |
891 | v.insert(9, 123); |
892 | let ob = v.to_object(py); |
893 | let dict: &PyDict = ob.downcast(py).unwrap(); |
894 | let mut key_sum = 0; |
895 | let mut value_sum = 0; |
896 | for (key, value) in dict { |
897 | key_sum += key.extract::<i32>().unwrap(); |
898 | value_sum += value.extract::<i32>().unwrap(); |
899 | } |
900 | assert_eq!(7 + 8 + 9, key_sum); |
901 | assert_eq!(32 + 42 + 123, value_sum); |
902 | }); |
903 | } |
904 | |
905 | #[test ] |
906 | fn test_hashmap_into_dict() { |
907 | Python::with_gil(|py| { |
908 | let mut map = HashMap::<i32, i32>::new(); |
909 | map.insert(1, 1); |
910 | |
911 | let py_map = map.into_py_dict(py); |
912 | |
913 | assert_eq!(py_map.len(), 1); |
914 | assert_eq!( |
915 | py_map |
916 | .get_item(1) |
917 | .unwrap() |
918 | .unwrap() |
919 | .extract::<i32>() |
920 | .unwrap(), |
921 | 1 |
922 | ); |
923 | }); |
924 | } |
925 | |
926 | #[test ] |
927 | fn test_btreemap_into_dict() { |
928 | Python::with_gil(|py| { |
929 | let mut map = BTreeMap::<i32, i32>::new(); |
930 | map.insert(1, 1); |
931 | |
932 | let py_map = map.into_py_dict(py); |
933 | |
934 | assert_eq!(py_map.len(), 1); |
935 | assert_eq!( |
936 | py_map |
937 | .get_item(1) |
938 | .unwrap() |
939 | .unwrap() |
940 | .extract::<i32>() |
941 | .unwrap(), |
942 | 1 |
943 | ); |
944 | }); |
945 | } |
946 | |
947 | #[test ] |
948 | fn test_vec_into_dict() { |
949 | Python::with_gil(|py| { |
950 | let vec = vec![("a" , 1), ("b" , 2), ("c" , 3)]; |
951 | let py_map = vec.into_py_dict(py); |
952 | |
953 | assert_eq!(py_map.len(), 3); |
954 | assert_eq!( |
955 | py_map |
956 | .get_item("b" ) |
957 | .unwrap() |
958 | .unwrap() |
959 | .extract::<i32>() |
960 | .unwrap(), |
961 | 2 |
962 | ); |
963 | }); |
964 | } |
965 | |
966 | #[test ] |
967 | fn test_slice_into_dict() { |
968 | Python::with_gil(|py| { |
969 | let arr = [("a" , 1), ("b" , 2), ("c" , 3)]; |
970 | let py_map = arr.into_py_dict(py); |
971 | |
972 | assert_eq!(py_map.len(), 3); |
973 | assert_eq!( |
974 | py_map |
975 | .get_item("b" ) |
976 | .unwrap() |
977 | .unwrap() |
978 | .extract::<i32>() |
979 | .unwrap(), |
980 | 2 |
981 | ); |
982 | }); |
983 | } |
984 | |
985 | #[test ] |
986 | fn dict_as_mapping() { |
987 | Python::with_gil(|py| { |
988 | let mut map = HashMap::<i32, i32>::new(); |
989 | map.insert(1, 1); |
990 | |
991 | let py_map = map.into_py_dict(py); |
992 | |
993 | assert_eq!(py_map.as_mapping().len().unwrap(), 1); |
994 | assert_eq!( |
995 | py_map |
996 | .as_mapping() |
997 | .get_item(1) |
998 | .unwrap() |
999 | .extract::<i32>() |
1000 | .unwrap(), |
1001 | 1 |
1002 | ); |
1003 | }); |
1004 | } |
1005 | |
1006 | #[cfg (not(PyPy))] |
1007 | fn abc_dict(py: Python<'_>) -> &PyDict { |
1008 | let mut map = HashMap::<&'static str, i32>::new(); |
1009 | map.insert("a" , 1); |
1010 | map.insert("b" , 2); |
1011 | map.insert("c" , 3); |
1012 | map.into_py_dict(py) |
1013 | } |
1014 | |
1015 | #[test ] |
1016 | #[cfg (not(PyPy))] |
1017 | fn dict_keys_view() { |
1018 | Python::with_gil(|py| { |
1019 | let dict = abc_dict(py); |
1020 | let keys = dict.call_method0("keys" ).unwrap(); |
1021 | assert!(keys.is_instance(py.get_type::<PyDictKeys>()).unwrap()); |
1022 | }) |
1023 | } |
1024 | |
1025 | #[test ] |
1026 | #[cfg (not(PyPy))] |
1027 | fn dict_values_view() { |
1028 | Python::with_gil(|py| { |
1029 | let dict = abc_dict(py); |
1030 | let values = dict.call_method0("values" ).unwrap(); |
1031 | assert!(values.is_instance(py.get_type::<PyDictValues>()).unwrap()); |
1032 | }) |
1033 | } |
1034 | |
1035 | #[test ] |
1036 | #[cfg (not(PyPy))] |
1037 | fn dict_items_view() { |
1038 | Python::with_gil(|py| { |
1039 | let dict = abc_dict(py); |
1040 | let items = dict.call_method0("items" ).unwrap(); |
1041 | assert!(items.is_instance(py.get_type::<PyDictItems>()).unwrap()); |
1042 | }) |
1043 | } |
1044 | |
1045 | #[test ] |
1046 | fn dict_update() { |
1047 | Python::with_gil(|py| { |
1048 | let dict = [("a" , 1), ("b" , 2), ("c" , 3)].into_py_dict(py); |
1049 | let other = [("b" , 4), ("c" , 5), ("d" , 6)].into_py_dict(py); |
1050 | dict.update(other.as_mapping()).unwrap(); |
1051 | assert_eq!(dict.len(), 4); |
1052 | assert_eq!( |
1053 | dict.get_item("a" ) |
1054 | .unwrap() |
1055 | .unwrap() |
1056 | .extract::<i32>() |
1057 | .unwrap(), |
1058 | 1 |
1059 | ); |
1060 | assert_eq!( |
1061 | dict.get_item("b" ) |
1062 | .unwrap() |
1063 | .unwrap() |
1064 | .extract::<i32>() |
1065 | .unwrap(), |
1066 | 4 |
1067 | ); |
1068 | assert_eq!( |
1069 | dict.get_item("c" ) |
1070 | .unwrap() |
1071 | .unwrap() |
1072 | .extract::<i32>() |
1073 | .unwrap(), |
1074 | 5 |
1075 | ); |
1076 | assert_eq!( |
1077 | dict.get_item("d" ) |
1078 | .unwrap() |
1079 | .unwrap() |
1080 | .extract::<i32>() |
1081 | .unwrap(), |
1082 | 6 |
1083 | ); |
1084 | |
1085 | assert_eq!(other.len(), 3); |
1086 | assert_eq!( |
1087 | other |
1088 | .get_item("b" ) |
1089 | .unwrap() |
1090 | .unwrap() |
1091 | .extract::<i32>() |
1092 | .unwrap(), |
1093 | 4 |
1094 | ); |
1095 | assert_eq!( |
1096 | other |
1097 | .get_item("c" ) |
1098 | .unwrap() |
1099 | .unwrap() |
1100 | .extract::<i32>() |
1101 | .unwrap(), |
1102 | 5 |
1103 | ); |
1104 | assert_eq!( |
1105 | other |
1106 | .get_item("d" ) |
1107 | .unwrap() |
1108 | .unwrap() |
1109 | .extract::<i32>() |
1110 | .unwrap(), |
1111 | 6 |
1112 | ); |
1113 | }) |
1114 | } |
1115 | |
1116 | #[test ] |
1117 | fn dict_update_if_missing() { |
1118 | Python::with_gil(|py| { |
1119 | let dict = [("a" , 1), ("b" , 2), ("c" , 3)].into_py_dict(py); |
1120 | let other = [("b" , 4), ("c" , 5), ("d" , 6)].into_py_dict(py); |
1121 | dict.update_if_missing(other.as_mapping()).unwrap(); |
1122 | assert_eq!(dict.len(), 4); |
1123 | assert_eq!( |
1124 | dict.get_item("a" ) |
1125 | .unwrap() |
1126 | .unwrap() |
1127 | .extract::<i32>() |
1128 | .unwrap(), |
1129 | 1 |
1130 | ); |
1131 | assert_eq!( |
1132 | dict.get_item("b" ) |
1133 | .unwrap() |
1134 | .unwrap() |
1135 | .extract::<i32>() |
1136 | .unwrap(), |
1137 | 2 |
1138 | ); |
1139 | assert_eq!( |
1140 | dict.get_item("c" ) |
1141 | .unwrap() |
1142 | .unwrap() |
1143 | .extract::<i32>() |
1144 | .unwrap(), |
1145 | 3 |
1146 | ); |
1147 | assert_eq!( |
1148 | dict.get_item("d" ) |
1149 | .unwrap() |
1150 | .unwrap() |
1151 | .extract::<i32>() |
1152 | .unwrap(), |
1153 | 6 |
1154 | ); |
1155 | |
1156 | assert_eq!(other.len(), 3); |
1157 | assert_eq!( |
1158 | other |
1159 | .get_item("b" ) |
1160 | .unwrap() |
1161 | .unwrap() |
1162 | .extract::<i32>() |
1163 | .unwrap(), |
1164 | 4 |
1165 | ); |
1166 | assert_eq!( |
1167 | other |
1168 | .get_item("c" ) |
1169 | .unwrap() |
1170 | .unwrap() |
1171 | .extract::<i32>() |
1172 | .unwrap(), |
1173 | 5 |
1174 | ); |
1175 | assert_eq!( |
1176 | other |
1177 | .get_item("d" ) |
1178 | .unwrap() |
1179 | .unwrap() |
1180 | .extract::<i32>() |
1181 | .unwrap(), |
1182 | 6 |
1183 | ); |
1184 | }) |
1185 | } |
1186 | } |
1187 | |