2912: Add `PyDict.update()` and `PyDict.update_if_missing()` r=davidhewitt a=samuelcolvin

Fix #2910

Note, I'd also be happy to remove the `override_` argument from merge and perhaps rename it to `update_missing` or similar to give a cleaner API. LMK what you think.

Please consider adding the following to your pull request:
 - [x] an entry for this PR in newsfragments
 - [x] docs to all new functions and / or detail in the guide
 - [x] tests for all new or changed functions


Co-authored-by: Samuel Colvin <s@muelcolvin.com>
This commit is contained in:
bors[bot] 2023-02-03 06:32:52 +00:00 committed by GitHub
commit 141cbeaa2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 0 deletions

View file

@ -0,0 +1 @@
Add `PyDict.update()` and `PyDict.update_if_missing()` methods.

View file

@ -257,6 +257,28 @@ impl PyDict {
pub fn as_mapping(&self) -> &PyMapping {
unsafe { self.downcast_unchecked() }
}
/// Update this dictionary with the key/value pairs from another.
///
/// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want
/// to use `self.update(other.as_mapping())`, note: `PyDict::as_mapping` is a zero-cost conversion.
pub fn update(&self, other: &PyMapping) -> PyResult<()> {
let py = self.py();
unsafe { err::error_on_minusone(py, ffi::PyDict_Update(self.as_ptr(), other.as_ptr())) }
}
/// Add key/value pairs from another dictionary to this one only when they do not exist in this.
///
/// This is equivalent to the Python expression `self.update({k: v for k, v in other.items() if k not in self})`.
/// If `other` is a `PyDict`, you may want to use `self.update_if_missing(other.as_mapping())`,
/// note: `PyDict::as_mapping` is a zero-cost conversion.
///
/// This method uses [`PyDict_Merge`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Merge) internally,
/// so should have the same performance as `update`.
pub fn update_if_missing(&self, other: &PyMapping) -> PyResult<()> {
let py = self.py();
unsafe { err::error_on_minusone(py, ffi::PyDict_Merge(self.as_ptr(), other.as_ptr(), 0)) }
}
}
/// PyO3 implementation of an iterator for a Python `dict` object.
@ -909,4 +931,42 @@ mod tests {
assert!(items.is_instance(py.get_type::<PyDictItems>()).unwrap());
})
}
#[test]
fn dict_update() {
Python::with_gil(|py| {
let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py);
let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py);
dict.update(other.as_mapping()).unwrap();
assert_eq!(dict.len(), 4);
assert_eq!(dict.get_item("a").unwrap().extract::<i32>().unwrap(), 1);
assert_eq!(dict.get_item("b").unwrap().extract::<i32>().unwrap(), 4);
assert_eq!(dict.get_item("c").unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(dict.get_item("d").unwrap().extract::<i32>().unwrap(), 6);
assert_eq!(other.len(), 3);
assert_eq!(other.get_item("b").unwrap().extract::<i32>().unwrap(), 4);
assert_eq!(other.get_item("c").unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(other.get_item("d").unwrap().extract::<i32>().unwrap(), 6);
})
}
#[test]
fn dict_update_if_missing() {
Python::with_gil(|py| {
let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict(py);
let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict(py);
dict.update_if_missing(other.as_mapping()).unwrap();
assert_eq!(dict.len(), 4);
assert_eq!(dict.get_item("a").unwrap().extract::<i32>().unwrap(), 1);
assert_eq!(dict.get_item("b").unwrap().extract::<i32>().unwrap(), 2);
assert_eq!(dict.get_item("c").unwrap().extract::<i32>().unwrap(), 3);
assert_eq!(dict.get_item("d").unwrap().extract::<i32>().unwrap(), 6);
assert_eq!(other.len(), 3);
assert_eq!(other.get_item("b").unwrap().extract::<i32>().unwrap(), 4);
assert_eq!(other.get_item("c").unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(other.get_item("d").unwrap().extract::<i32>().unwrap(), 6);
})
}
}