diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ebfabf7803..b20cecfb946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `PyAny::py` as a convenience for `PyNativeType::py`. [#1751](https://github.com/PyO3/pyo3/pull/1751) - Add implementation of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1825](https://github.com/PyO3/pyo3/pull/1825) - Add range indexing implementations of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1829](https://github.com/PyO3/pyo3/pull/1829) +- Add commonly-used sequence methods to `PyList` and `PyTuple`. [#1849](https://github.com/PyO3/pyo3/pull/1849) ### Changed diff --git a/src/types/list.rs b/src/types/list.rs index abcdb63ab51..6cc23247913 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -5,8 +5,10 @@ use crate::err::{self, PyResult}; use crate::ffi::{self, Py_ssize_t}; use crate::internal_tricks::get_ssize_index; +use crate::types::PySequence; use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, + AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyObject, PyTryFrom, Python, ToBorrowedObject, + ToPyObject, }; /// Represents a Python `list`. @@ -132,6 +134,40 @@ impl PyList { } } + /// Deletes the `index`th element of self. + /// + /// This is equivalent to the Python statement `del self[i]`. + #[inline] + pub fn del_item(&self, index: usize) -> PyResult<()> { + unsafe { PySequence::try_from_unchecked(self).del_item(index) } + } + + /// Assigns the sequence `seq` to the slice of `self` from `low` to `high`. + /// + /// This is equivalent to the Python statement `self[low:high] = v`. + #[inline] + pub fn set_slice(&self, low: usize, high: usize, seq: &PyAny) -> PyResult<()> { + unsafe { + err::error_on_minusone( + self.py(), + ffi::PyList_SetSlice( + self.as_ptr(), + get_ssize_index(low), + get_ssize_index(high), + seq.as_ptr(), + ), + ) + } + } + + /// Deletes the slice from `low` to `high` from `self`. + /// + /// This is equivalent to the Python statement `del self[low:high]`. + #[inline] + pub fn del_slice(&self, low: usize, high: usize) -> PyResult<()> { + unsafe { PySequence::try_from_unchecked(self).del_slice(low, high) } + } + /// Appends an item to the list. pub fn append(&self, item: I) -> PyResult<()> where @@ -157,6 +193,28 @@ impl PyList { }) } + /// Determines if self contains `value`. + /// + /// This is equivalent to the Python expression `value in self`. + #[inline] + pub fn contains(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + unsafe { PySequence::try_from_unchecked(self).contains(value) } + } + + /// Returns the first index `i` for which `self[i] == value`. + /// + /// This is equivalent to the Python expression `self.index(value)`. + #[inline] + pub fn index(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + unsafe { PySequence::try_from_unchecked(self).index(value) } + } + /// Returns an iterator over this list's items. pub fn iter(&self) -> PyListIterator { PyListIterator { @@ -583,4 +641,79 @@ mod tests { list[8..].extract::>().unwrap(); }) } + + #[test] + fn test_list_del_item() { + Python::with_gil(|py| { + let list = PyList::new(py, &[1, 1, 2, 3, 5, 8]); + assert!(list.del_item(10).is_err()); + assert_eq!(1, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(1, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(2, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(3, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(5, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(8, list[0].extract::().unwrap()); + assert!(list.del_item(0).is_ok()); + assert_eq!(0, list.len()); + assert!(list.del_item(0).is_err()); + }); + } + + #[test] + fn test_list_set_slice() { + Python::with_gil(|py| { + let list = PyList::new(py, &[1, 1, 2, 3, 5, 8]); + let ins = PyList::new(py, &[7, 4]); + list.set_slice(1, 4, ins).unwrap(); + assert_eq!([1, 7, 4, 5, 8], list.extract::<[i32; 5]>().unwrap()); + list.set_slice(3, 100, PyList::empty(py)).unwrap(); + assert_eq!([1, 7, 4], list.extract::<[i32; 3]>().unwrap()); + }); + } + + #[test] + fn test_list_del_slice() { + Python::with_gil(|py| { + let list = PyList::new(py, &[1, 1, 2, 3, 5, 8]); + list.del_slice(1, 4).unwrap(); + assert_eq!([1, 5, 8], list.extract::<[i32; 3]>().unwrap()); + list.del_slice(1, 100).unwrap(); + assert_eq!([1], list.extract::<[i32; 1]>().unwrap()); + }); + } + + #[test] + fn test_list_contains() { + Python::with_gil(|py| { + let list = PyList::new(py, &[1, 1, 2, 3, 5, 8]); + assert_eq!(6, list.len()); + + let bad_needle = 7i32.to_object(py); + assert!(!list.contains(&bad_needle).unwrap()); + + let good_needle = 8i32.to_object(py); + assert!(list.contains(&good_needle).unwrap()); + + let type_coerced_needle = 8f32.to_object(py); + assert!(list.contains(&type_coerced_needle).unwrap()); + }); + } + + #[test] + fn test_list_index() { + Python::with_gil(|py| { + let list = PyList::new(py, &[1, 1, 2, 3, 5, 8]); + assert_eq!(0, list.index(1i32).unwrap()); + assert_eq!(2, list.index(2i32).unwrap()); + assert_eq!(3, list.index(3i32).unwrap()); + assert_eq!(4, list.index(5i32).unwrap()); + assert_eq!(5, list.index(8i32).unwrap()); + assert!(list.index(42i32).is_err()); + }); + } } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index c844382cd51..de753e5c475 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -2,9 +2,10 @@ use crate::ffi::{self, Py_ssize_t}; use crate::internal_tricks::get_ssize_index; +use crate::types::PySequence; use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyObject, - PyResult, PyTryFrom, Python, ToPyObject, + PyResult, PyTryFrom, Python, ToBorrowedObject, ToPyObject, }; /// Represents a Python `tuple` object. @@ -144,6 +145,28 @@ impl PyTuple { } } + /// Determines if self contains `value`. + /// + /// This is equivalent to the Python expression `value in self`. + #[inline] + pub fn contains(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + unsafe { PySequence::try_from_unchecked(self).contains(value) } + } + + /// Returns the first index `i` for which `self[i] == value`. + /// + /// This is equivalent to the Python expression `self.index(value)`. + #[inline] + pub fn index(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + unsafe { PySequence::try_from_unchecked(self).index(value) } + } + /// Returns an iterator over the tuple items. pub fn iter(&self) -> PyTupleIterator { PyTupleIterator { @@ -635,4 +658,36 @@ mod tests { tuple[8..].extract::>().unwrap(); }) } + + #[test] + fn test_tuple_contains() { + Python::with_gil(|py| { + let ob = (1, 1, 2, 3, 5, 8).to_object(py); + let tuple = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(6, tuple.len()); + + let bad_needle = 7i32.to_object(py); + assert!(!tuple.contains(&bad_needle).unwrap()); + + let good_needle = 8i32.to_object(py); + assert!(tuple.contains(&good_needle).unwrap()); + + let type_coerced_needle = 8f32.to_object(py); + assert!(tuple.contains(&type_coerced_needle).unwrap()); + }); + } + + #[test] + fn test_tuple_index() { + Python::with_gil(|py| { + let ob = (1, 1, 2, 3, 5, 8).to_object(py); + let tuple = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(0, tuple.index(1i32).unwrap()); + assert_eq!(2, tuple.index(2i32).unwrap()); + assert_eq!(3, tuple.index(3i32).unwrap()); + assert_eq!(4, tuple.index(5i32).unwrap()); + assert_eq!(5, tuple.index(8i32).unwrap()); + assert!(tuple.index(42i32).is_err()); + }); + } }