Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyList: add more sequence APIs #1849

Merged
merged 1 commit into from Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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<usize>` 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

Expand Down
135 changes: 134 additions & 1 deletion src/types/list.rs
Expand Up @@ -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`.
Expand Down Expand Up @@ -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<I>(&self, item: I) -> PyResult<()>
where
Expand All @@ -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<V>(&self, value: V) -> PyResult<bool>
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<V>(&self, value: V) -> PyResult<usize>
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 {
Expand Down Expand Up @@ -583,4 +641,79 @@ mod tests {
list[8..].extract::<Vec<i32>>().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::<i32>().unwrap());
assert!(list.del_item(0).is_ok());
assert_eq!(1, list[0].extract::<i32>().unwrap());
assert!(list.del_item(0).is_ok());
assert_eq!(2, list[0].extract::<i32>().unwrap());
assert!(list.del_item(0).is_ok());
assert_eq!(3, list[0].extract::<i32>().unwrap());
assert!(list.del_item(0).is_ok());
assert_eq!(5, list[0].extract::<i32>().unwrap());
assert!(list.del_item(0).is_ok());
assert_eq!(8, list[0].extract::<i32>().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());
});
}
}
57 changes: 56 additions & 1 deletion src/types/tuple.rs
Expand Up @@ -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.
Expand Down Expand Up @@ -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<V>(&self, value: V) -> PyResult<bool>
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<V>(&self, value: V) -> PyResult<usize>
where
V: ToBorrowedObject,
{
unsafe { PySequence::try_from_unchecked(self).index(value) }
}

/// Returns an iterator over the tuple items.
pub fn iter(&self) -> PyTupleIterator {
PyTupleIterator {
Expand Down Expand Up @@ -635,4 +658,36 @@ mod tests {
tuple[8..].extract::<Vec<i32>>().unwrap();
})
}

#[test]
fn test_tuple_contains() {
Python::with_gil(|py| {
let ob = (1, 1, 2, 3, 5, 8).to_object(py);
let tuple = <PyTuple as PyTryFrom>::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 = <PyTuple as PyTryFrom>::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());
});
}
}