diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eaf1b841e2..746045840dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `PyList::get_item_unchecked()` and `PyTuple::get_item_unchecked()` to get items without bounds checks. [#1733](https://github.com/PyO3/pyo3/pull/1733) - 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) ### Changed diff --git a/guide/src/migration.md b/guide/src/migration.md index b0855381076..de424e4bdef 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,25 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.14.* to 0.15 + +### Changes in sequence indexing + +For all types that take sequence indices (`PyList`, `PyTuple` and `PySequence`), +the API has been made consistent to only take `usize` indices, for consistency +with Rust's indexing conventions. Negative indices, which were only +sporadically supported even in APIs that took `isize`, now aren't supported +anywhere. + +Further, the `get_item` methods now always return a `PyResult` instead of +panicking on invalid indices. The `Index` trait has been implemented instead, +and provides the same panic behavior as on Rust vectors. + +Note that *slice* indices (accepted by `PySequence::get_slice` and other) still +inherit the Python behavior of clamping the indices to the actual length, and +not panicking/returning an error on out of range indices. + + ## from 0.13.* to 0.14 ### `auto-initialize` feature is now opt-in diff --git a/src/types/list.rs b/src/types/list.rs index 6bc724121eb..284b7b2d682 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -8,6 +8,7 @@ use crate::internal_tricks::get_ssize_index; use crate::{ AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, }; +use std::ops::Index; /// Represents a Python `list`. #[repr(transparent)] @@ -176,6 +177,20 @@ impl PyList { } } +impl Index for PyList { + type Output = PyAny; + + fn index(&self, index: usize) -> &Self::Output { + self.get_item(index).unwrap_or_else(|_| { + panic!( + "index {} out of range for list of length {}", + index, + self.len() + ); + }) + } +} + /// Used by `PyList::iter()`. pub struct PyListIterator<'a> { list: &'a PyList, @@ -189,7 +204,7 @@ impl<'a> Iterator for PyListIterator<'a> { fn next(&mut self) -> Option<&'a PyAny> { if self.index < self.list.len() { #[cfg(any(Py_LIMITED_API, PyPy))] - let item = self.list.get_item(self.index).expect("tuple.get failed"); + let item = self.list.get_item(self.index).expect("list.get failed"); #[cfg(not(any(Py_LIMITED_API, PyPy)))] let item = unsafe { self.list.get_item_unchecked(self.index) }; self.index += 1; @@ -256,10 +271,10 @@ mod tests { fn test_new() { Python::with_gil(|py| { let list = PyList::new(py, &[2, 3, 5, 7]); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(5, list.get_item(2).unwrap().extract::().unwrap()); - assert_eq!(7, list.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); + assert_eq!(5, list[2].extract::().unwrap()); + assert_eq!(7, list[3].extract::().unwrap()); }); } @@ -299,9 +314,9 @@ mod tests { let list = PyList::new(py, &[2, 3, 5, 7]); let val = 42i32.to_object(py); let val2 = 42i32.to_object(py); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); list.set_item(0, val).unwrap(); - assert_eq!(42, list.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(42, list[0].extract::().unwrap()); assert!(list.set_item(10, val2).is_err()); }); } @@ -331,13 +346,13 @@ mod tests { let val = 42i32.to_object(py); let val2 = 43i32.to_object(py); assert_eq!(4, list.len()); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); list.insert(0, val).unwrap(); list.insert(1000, val2).unwrap(); assert_eq!(6, list.len()); - assert_eq!(42, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(2, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(43, list.get_item(5).unwrap().extract::().unwrap()); + assert_eq!(42, list[0].extract::().unwrap()); + assert_eq!(2, list[1].extract::().unwrap()); + assert_eq!(43, list[5].extract::().unwrap()); }); } @@ -362,8 +377,8 @@ mod tests { Python::with_gil(|py| { let list = PyList::new(py, &[2]); list.append(3).unwrap(); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(1).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); }); } @@ -440,15 +455,15 @@ mod tests { Python::with_gil(|py| { let v = vec![7, 3, 2, 5]; let list = PyList::new(py, &v); - assert_eq!(7, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(2, list.get_item(2).unwrap().extract::().unwrap()); - assert_eq!(5, list.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(7, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); + assert_eq!(2, list[2].extract::().unwrap()); + assert_eq!(5, list[3].extract::().unwrap()); list.sort().unwrap(); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(5, list.get_item(2).unwrap().extract::().unwrap()); - assert_eq!(7, list.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); + assert_eq!(5, list[2].extract::().unwrap()); + assert_eq!(7, list[3].extract::().unwrap()); }); } @@ -457,15 +472,15 @@ mod tests { Python::with_gil(|py| { let v = vec![2, 3, 5, 7]; let list = PyList::new(py, &v); - assert_eq!(2, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(5, list.get_item(2).unwrap().extract::().unwrap()); - assert_eq!(7, list.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(2, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); + assert_eq!(5, list[2].extract::().unwrap()); + assert_eq!(7, list[3].extract::().unwrap()); list.reverse().unwrap(); - assert_eq!(7, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(5, list.get_item(1).unwrap().extract::().unwrap()); - assert_eq!(3, list.get_item(2).unwrap().extract::().unwrap()); - assert_eq!(2, list.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(7, list[0].extract::().unwrap()); + assert_eq!(5, list[1].extract::().unwrap()); + assert_eq!(3, list[2].extract::().unwrap()); + assert_eq!(2, list[3].extract::().unwrap()); }); } @@ -474,8 +489,8 @@ mod tests { Python::with_gil(|py| { let array: PyObject = [1, 2].into_py(py); let list = ::try_from(array.as_ref(py)).unwrap(); - assert_eq!(1, list.get_item(0).unwrap().extract::().unwrap()); - assert_eq!(2, list.get_item(1).unwrap().extract::().unwrap()); + assert_eq!(1, list[0].extract::().unwrap()); + assert_eq!(2, list[1].extract::().unwrap()); }); } @@ -510,4 +525,23 @@ mod tests { assert_eq!(obj.extract::().unwrap(), 2); }); } + + #[test] + fn test_list_index_trait() { + Python::with_gil(|py| { + let list = PyList::new(py, &[2, 3, 5]); + assert_eq!(2, list[0].extract::().unwrap()); + assert_eq!(3, list[1].extract::().unwrap()); + assert_eq!(5, list[2].extract::().unwrap()); + }); + } + + #[test] + #[should_panic] + fn test_list_index_trait_panic() { + Python::with_gil(|py| { + let list = PyList::new(py, &[2, 3, 5]); + let _ = &list[7]; + }); + } } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 58ffbe854b2..df0267243b7 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -6,6 +6,7 @@ use crate::internal_tricks::get_ssize_index; use crate::types::{PyAny, PyList, PyTuple}; use crate::AsPyPointer; use crate::{FromPyObject, PyTryFrom, ToBorrowedObject}; +use std::ops::Index; /// Represents a reference to a Python object supporting the sequence protocol. #[repr(transparent)] @@ -268,6 +269,16 @@ impl PySequence { } } +impl Index for PySequence { + type Output = PyAny; + + fn index(&self, index: usize) -> &Self::Output { + self.get_item(index).unwrap_or_else(|_| { + panic!("index {} out of range for sequence", index); + }) + } +} + impl<'a, T> FromPyObject<'a> for Vec where T: FromPyObject<'a>, @@ -430,6 +441,29 @@ mod tests { }); } + #[test] + fn test_seq_index_trait() { + Python::with_gil(|py| { + let v: Vec = vec![1, 1, 2]; + let ob = v.to_object(py); + let seq = ob.cast_as::(py).unwrap(); + assert_eq!(1, seq[0].extract::().unwrap()); + assert_eq!(1, seq[1].extract::().unwrap()); + assert_eq!(2, seq[2].extract::().unwrap()); + }); + } + + #[test] + #[should_panic] + fn test_seq_index_trait_panic() { + Python::with_gil(|py| { + let v: Vec = vec![1, 1, 2]; + let ob = v.to_object(py); + let seq = ob.cast_as::(py).unwrap(); + let _ = &seq[7]; + }); + } + #[test] fn test_seq_del_item() { Python::with_gil(|py| { @@ -437,17 +471,17 @@ mod tests { let ob = v.to_object(py); let seq = ob.cast_as::(py).unwrap(); assert!(seq.del_item(10).is_err()); - assert_eq!(1, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(1, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); - assert_eq!(1, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(1, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); - assert_eq!(2, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(2, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); - assert_eq!(3, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(3, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); - assert_eq!(5, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(5, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); - assert_eq!(8, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(8, seq[0].extract::().unwrap()); assert!(seq.del_item(0).is_ok()); assert_eq!(0, seq.len().unwrap()); assert!(seq.del_item(0).is_err()); @@ -460,9 +494,9 @@ mod tests { let v: Vec = vec![1, 2]; let ob = v.to_object(py); let seq = ob.cast_as::(py).unwrap(); - assert_eq!(2, seq.get_item(1).unwrap().extract::().unwrap()); + assert_eq!(2, seq[1].extract::().unwrap()); assert!(seq.set_item(1, 10).is_ok()); - assert_eq!(10, seq.get_item(1).unwrap().extract::().unwrap()); + assert_eq!(10, seq[1].extract::().unwrap()); }); } @@ -475,7 +509,7 @@ mod tests { let ob = v.to_object(py); let seq = ob.cast_as::(py).unwrap(); assert!(seq.set_item(1, &obj).is_ok()); - assert!(seq.get_item(1).unwrap().as_ptr() == obj.as_ptr()); + assert!(seq[1].as_ptr() == obj.as_ptr()); }); Python::with_gil(|py| { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 5b1463206df..2506ec732f7 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -6,6 +6,7 @@ use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyObject, PyResult, PyTryFrom, Python, ToPyObject, }; +use std::ops::Index; /// Represents a Python `tuple` object. /// @@ -139,6 +140,20 @@ impl PyTuple { } } +impl Index for PyTuple { + type Output = PyAny; + + fn index(&self, index: usize) -> &Self::Output { + self.get_item(index).unwrap_or_else(|_| { + panic!( + "index {} out of range for tuple of length {}", + index, + self.len() + ); + }) + } +} + /// Used by `PyTuple::iter()`. pub struct PyTupleIterator<'a> { tuple: &'a PyTuple, @@ -537,4 +552,25 @@ mod tests { assert_eq!(obj.extract::().unwrap(), 1); }); } + + #[test] + fn test_tuple_index_trait() { + Python::with_gil(|py| { + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(1, tuple[0].extract::().unwrap()); + assert_eq!(2, tuple[1].extract::().unwrap()); + assert_eq!(3, tuple[2].extract::().unwrap()); + }); + } + + #[test] + #[should_panic] + fn test_tuple_index_trait_panic() { + Python::with_gil(|py| { + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_ref(py)).unwrap(); + let _ = &tuple[7]; + }); + } }