cpython/objects/
dict.rs

1// Copyright (c) 2015 Daniel Grunwald
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19use std::{cmp, collections, hash, ptr};
20
21use crate::conversion::ToPyObject;
22use crate::err::{self, PyErr, PyResult};
23use crate::ffi;
24use crate::objects::{PyList, PyObject};
25use crate::python::{Python, PythonObject};
26
27/// Represents a Python `dict`.
28pub struct PyDict(PyObject);
29
30pyobject_newtype!(PyDict, PyDict_Check, PyDict_Type);
31
32impl PyDict {
33    /// Creates a new empty dictionary.
34    ///
35    /// May panic when running out of memory.
36    pub fn new(py: Python) -> PyDict {
37        unsafe { err::cast_from_owned_ptr_or_panic(py, ffi::PyDict_New()) }
38    }
39
40    /// Return a new dictionary that contains the same key-value pairs as self.
41    /// Corresponds to `dict(self)` in Python.
42    pub fn copy(&self, py: Python) -> PyResult<PyDict> {
43        unsafe { err::result_cast_from_owned_ptr(py, ffi::PyDict_Copy(self.0.as_ptr())) }
44    }
45
46    /// Empty an existing dictionary of all key-value pairs.
47    #[inline]
48    pub fn clear(&self, _py: Python) {
49        unsafe { ffi::PyDict_Clear(self.0.as_ptr()) }
50    }
51
52    /// Return the number of items in the dictionary.
53    /// This is equivalent to len(p) on a dictionary.
54    #[inline]
55    pub fn len(&self, _py: Python) -> usize {
56        unsafe { ffi::PyDict_Size(self.0.as_ptr()) as usize }
57    }
58
59    /// Determine if the dictionary contains the specified key.
60    /// This is equivalent to the Python expression `key in self`.
61    pub fn contains<K>(&self, py: Python, key: K) -> PyResult<bool>
62    where
63        K: ToPyObject,
64    {
65        key.with_borrowed_ptr(py, |key| unsafe {
66            match ffi::PyDict_Contains(self.0.as_ptr(), key) {
67                1 => Ok(true),
68                0 => Ok(false),
69                _ => Err(PyErr::fetch(py)),
70            }
71        })
72    }
73
74    /// Gets an item from the dictionary.
75    /// Returns None if the item is not present, or if an error occurs.
76    pub fn get_item<K>(&self, py: Python, key: K) -> Option<PyObject>
77    where
78        K: ToPyObject,
79    {
80        key.with_borrowed_ptr(py, |key| unsafe {
81            PyObject::from_borrowed_ptr_opt(py, ffi::PyDict_GetItem(self.0.as_ptr(), key))
82        })
83    }
84
85    /// Sets an item value.
86    /// This is equivalent to the Python expression `self[key] = value`.
87    pub fn set_item<K, V>(&self, py: Python, key: K, value: V) -> PyResult<()>
88    where
89        K: ToPyObject,
90        V: ToPyObject,
91    {
92        key.with_borrowed_ptr(py, move |key| {
93            value.with_borrowed_ptr(py, |value| unsafe {
94                err::error_on_minusone(py, ffi::PyDict_SetItem(self.0.as_ptr(), key, value))
95            })
96        })
97    }
98
99    /// Deletes an item.
100    /// This is equivalent to the Python expression `del self[key]`.
101    pub fn del_item<K>(&self, py: Python, key: K) -> PyResult<()>
102    where
103        K: ToPyObject,
104    {
105        key.with_borrowed_ptr(py, |key| unsafe {
106            err::error_on_minusone(py, ffi::PyDict_DelItem(self.0.as_ptr(), key))
107        })
108    }
109
110    // List of dict items.
111    // This is equivalent to the python expression `list(dict.items())`.
112    pub fn items_list(&self, py: Python) -> PyList {
113        unsafe { err::cast_from_owned_ptr_or_panic(py, ffi::PyDict_Items(self.0.as_ptr())) }
114    }
115
116    /// Returns the list of (key,value) pairs in this dictionary.
117    pub fn items(&self, py: Python) -> Vec<(PyObject, PyObject)> {
118        // Note that we don't provide an iterator because
119        // PyDict_Next() is unsafe to use when the dictionary might be changed
120        // by other python code.
121        let mut vec = Vec::with_capacity(self.len(py));
122        let mut pos = 0;
123        let mut key: *mut ffi::PyObject = ptr::null_mut();
124        let mut value: *mut ffi::PyObject = ptr::null_mut();
125        unsafe {
126            while ffi::PyDict_Next(self.0.as_ptr(), &mut pos, &mut key, &mut value) != 0 {
127                vec.push((
128                    PyObject::from_borrowed_ptr(py, key),
129                    PyObject::from_borrowed_ptr(py, value),
130                ));
131            }
132        }
133        vec
134    }
135}
136
137/// Converts a Rust `HashMap` to a Python `dict`.
138impl<K, V, H> ToPyObject for collections::HashMap<K, V, H>
139where
140    K: hash::Hash + cmp::Eq + ToPyObject,
141    V: ToPyObject,
142    H: hash::BuildHasher,
143{
144    type ObjectType = PyDict;
145
146    fn to_py_object(&self, py: Python) -> PyDict {
147        let dict = PyDict::new(py);
148        for (key, value) in self {
149            dict.set_item(py, key, value).unwrap();
150        }
151        dict
152    }
153}
154
155/// Converts a Rust `BTreeMap` to a Python `dict`.
156impl<K, V> ToPyObject for collections::BTreeMap<K, V>
157where
158    K: cmp::Eq + ToPyObject,
159    V: ToPyObject,
160{
161    type ObjectType = PyDict;
162
163    fn to_py_object(&self, py: Python) -> PyDict {
164        let dict = PyDict::new(py);
165        for (key, value) in self {
166            dict.set_item(py, key, value).unwrap();
167        }
168        dict
169    }
170}
171
172#[cfg(test)]
173mod test {
174    use crate::conversion::ToPyObject;
175    use crate::objects::{PyDict, PyTuple};
176    use crate::python::{Python, PythonObject};
177    use std::collections::HashMap;
178
179    #[test]
180    fn test_len() {
181        let gil = Python::acquire_gil();
182        let py = gil.python();
183        let mut v = HashMap::new();
184        let dict = v.to_py_object(py);
185        assert_eq!(0, dict.len(py));
186        v.insert(7, 32);
187        let dict2 = v.to_py_object(py);
188        assert_eq!(1, dict2.len(py));
189    }
190
191    #[test]
192    fn test_contains() {
193        let gil = Python::acquire_gil();
194        let py = gil.python();
195        let mut v = HashMap::new();
196        v.insert(7, 32);
197        let dict = v.to_py_object(py);
198        assert_eq!(true, dict.contains(py, 7i32).unwrap());
199        assert_eq!(false, dict.contains(py, 8i32).unwrap());
200    }
201
202    #[test]
203    fn test_get_item() {
204        let gil = Python::acquire_gil();
205        let py = gil.python();
206        let mut v = HashMap::new();
207        v.insert(7, 32);
208        let dict = v.to_py_object(py);
209        assert_eq!(
210            32,
211            dict.get_item(py, 7i32).unwrap().extract::<i32>(py).unwrap()
212        );
213        assert_eq!(None, dict.get_item(py, 8i32));
214    }
215
216    #[test]
217    fn test_set_item() {
218        let gil = Python::acquire_gil();
219        let py = gil.python();
220        let mut v = HashMap::new();
221        v.insert(7, 32);
222        let dict = v.to_py_object(py);
223        assert!(dict.set_item(py, 7i32, 42i32).is_ok()); // change
224        assert!(dict.set_item(py, 8i32, 123i32).is_ok()); // insert
225        assert_eq!(
226            42i32,
227            dict.get_item(py, 7i32).unwrap().extract::<i32>(py).unwrap()
228        );
229        assert_eq!(
230            123i32,
231            dict.get_item(py, 8i32).unwrap().extract::<i32>(py).unwrap()
232        );
233    }
234
235    #[test]
236    fn test_set_item_does_not_update_original_object() {
237        let gil = Python::acquire_gil();
238        let py = gil.python();
239        let mut v = HashMap::new();
240        v.insert(7, 32);
241        let dict = v.to_py_object(py);
242        assert!(dict.set_item(py, 7i32, 42i32).is_ok()); // change
243        assert!(dict.set_item(py, 8i32, 123i32).is_ok()); // insert
244        assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated!
245        assert_eq!(None, v.get(&8i32));
246    }
247
248    #[test]
249    fn test_del_item() {
250        let gil = Python::acquire_gil();
251        let py = gil.python();
252        let mut v = HashMap::new();
253        v.insert(7, 32);
254        let dict = v.to_py_object(py);
255        assert!(dict.del_item(py, 7i32).is_ok());
256        assert_eq!(0, dict.len(py));
257        assert_eq!(None, dict.get_item(py, 7i32));
258    }
259
260    #[test]
261    fn test_del_item_does_not_update_original_object() {
262        let gil = Python::acquire_gil();
263        let py = gil.python();
264        let mut v = HashMap::new();
265        v.insert(7, 32);
266        let dict = v.to_py_object(py);
267        assert!(dict.del_item(py, 7i32).is_ok()); // change
268        assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated!
269    }
270
271    #[test]
272    fn test_items_list() {
273        let gil = Python::acquire_gil();
274        let py = gil.python();
275        let mut v = HashMap::new();
276        v.insert(7, 32);
277        v.insert(8, 42);
278        v.insert(9, 123);
279        let dict = v.to_py_object(py);
280        // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
281        let mut key_sum = 0;
282        let mut value_sum = 0;
283        for el in dict.items_list(py).iter(py) {
284            let tuple = el.cast_into::<PyTuple>(py).unwrap();
285            key_sum += tuple.get_item(py, 0).extract::<i32>(py).unwrap();
286            value_sum += tuple.get_item(py, 1).extract::<i32>(py).unwrap();
287        }
288        assert_eq!(7 + 8 + 9, key_sum);
289        assert_eq!(32 + 42 + 123, value_sum);
290    }
291
292    #[test]
293    fn test_items() {
294        let gil = Python::acquire_gil();
295        let py = gil.python();
296        let mut v = HashMap::new();
297        v.insert(7, 32);
298        v.insert(8, 42);
299        v.insert(9, 123);
300        let dict = v.to_py_object(py);
301        // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
302        let mut key_sum = 0;
303        let mut value_sum = 0;
304        for (key, value) in dict.items(py) {
305            key_sum += key.extract::<i32>(py).unwrap();
306            value_sum += value.extract::<i32>(py).unwrap();
307        }
308        assert_eq!(7 + 8 + 9, key_sum);
309        assert_eq!(32 + 42 + 123, value_sum);
310    }
311}