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

Implement DoubleEndedIterator for PyTupleIterator #3366

Merged
merged 2 commits into from
Aug 8, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/3366.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add implementations `DoubleEndedIterator` for `PyTupleIterator` and `PyListIterator`.
1 change: 1 addition & 0 deletions newsfragments/3366.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `PyTupleIterator` type returned by `PyTuple::iter` is now public and hence can be named by downstream crates.
2 changes: 1 addition & 1 deletion src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ impl<T: Element> PyBuffer<T> {

err::error_on_minusone(py, unsafe {
ffi::PyBuffer_ToContiguous(
target.as_ptr() as *mut raw::c_void,
target.as_mut_ptr().cast(),
#[cfg(Py_3_11)]
&*self.0,
#[cfg(not(Py_3_11))]
Expand Down
75 changes: 66 additions & 9 deletions src/types/list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::convert::TryInto;
use std::iter::FusedIterator;

use crate::err::{self, PyResult};
use crate::ffi::{self, Py_ssize_t};
Expand Down Expand Up @@ -264,6 +265,7 @@ impl PyList {
PyListIterator {
list: self,
index: 0,
length: self.len(),
}
}

Expand Down Expand Up @@ -291,18 +293,28 @@ index_impls!(PyList, "list", PyList::len, PyList::get_slice);
pub struct PyListIterator<'a> {
list: &'a PyList,
index: usize,
length: usize,
}

impl<'a> PyListIterator<'a> {
unsafe fn get_item(&self, index: usize) -> &'a PyAny {
#[cfg(any(Py_LIMITED_API, PyPy))]
let item = self.list.get_item(index).expect("list.get failed");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let item = self.list.get_item_unchecked(index);
item
}
}

impl<'a> Iterator for PyListIterator<'a> {
type Item = &'a PyAny;

#[inline]
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("list.get failed");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let item = unsafe { self.list.get_item_unchecked(self.index) };
fn next(&mut self) -> Option<Self::Item> {
let length = self.length.min(self.list.len());

if self.index < length {
let item = unsafe { self.get_item(self.index) };
self.index += 1;
Some(item)
} else {
Expand All @@ -317,13 +329,30 @@ impl<'a> Iterator for PyListIterator<'a> {
}
}

impl<'a> DoubleEndedIterator for PyListIterator<'a> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let length = self.length.min(self.list.len());

if self.index < length {
let item = unsafe { self.get_item(length - 1) };
self.length = length - 1;
Some(item)
} else {
None
}
}
}

impl<'a> ExactSizeIterator for PyListIterator<'a> {
fn len(&self) -> usize {
self.list.len().saturating_sub(self.index)
self.length.saturating_sub(self.index)
}
}

impl<'a> std::iter::IntoIterator for &'a PyList {
impl FusedIterator for PyListIterator<'_> {}

impl<'a> IntoIterator for &'a PyList {
type Item = &'a PyAny;
type IntoIter = PyListIterator<'a>;

Expand Down Expand Up @@ -494,13 +523,41 @@ mod tests {
iter.next();
assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1)));

// Exhust iterator.
// Exhaust iterator.
for _ in &mut iter {}

assert_eq!(iter.size_hint(), (0, Some(0)));
});
}

#[test]
fn test_iter_rev() {
Python::with_gil(|py| {
let v = vec![2, 3, 5, 7];
let ob = v.to_object(py);
let list: &PyList = ob.downcast(py).unwrap();

let mut iter = list.iter().rev();

assert_eq!(iter.size_hint(), (4, Some(4)));

assert_eq!(iter.next().unwrap().extract::<i32>().unwrap(), 7);
assert_eq!(iter.size_hint(), (3, Some(3)));

assert_eq!(iter.next().unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(iter.size_hint(), (2, Some(2)));

assert_eq!(iter.next().unwrap().extract::<i32>().unwrap(), 3);
assert_eq!(iter.size_hint(), (1, Some(1)));

assert_eq!(iter.next().unwrap().extract::<i32>().unwrap(), 2);
assert_eq!(iter.size_hint(), (0, Some(0)));

assert!(iter.next().is_none());
assert!(iter.next().is_none());
});
}

#[test]
fn test_into_iter() {
Python::with_gil(|py| {
Expand Down
1 change: 1 addition & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub mod iter {
pub use super::dict::PyDictIterator;
pub use super::frozenset::PyFrozenSetIterator;
pub use super::set::PySetIterator;
pub use super::tuple::PyTupleIterator;
adamreichold marked this conversation as resolved.
Show resolved Hide resolved
}

// Implementations core to all native types
Expand Down
60 changes: 55 additions & 5 deletions src/types/tuple.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::convert::TryInto;
use std::iter::FusedIterator;

use crate::ffi::{self, Py_ssize_t};
#[cfg(feature = "experimental-inspect")]
Expand Down Expand Up @@ -232,16 +233,23 @@ pub struct PyTupleIterator<'a> {
length: usize,
}

impl<'a> PyTupleIterator<'a> {
unsafe fn get_item(&self, index: usize) -> &'a PyAny {
#[cfg(any(Py_LIMITED_API, PyPy))]
let item = self.tuple.get_item(index).expect("tuple.get failed");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let item = self.tuple.get_item_unchecked(index);
item
}
}

impl<'a> Iterator for PyTupleIterator<'a> {
type Item = &'a PyAny;

#[inline]
fn next(&mut self) -> Option<&'a PyAny> {
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.length {
#[cfg(any(Py_LIMITED_API, PyPy))]
let item = self.tuple.get_item(self.index).expect("tuple.get failed");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let item = unsafe { self.tuple.get_item_unchecked(self.index) };
let item = unsafe { self.get_item(self.index) };
self.index += 1;
Some(item)
} else {
Expand All @@ -256,12 +264,27 @@ impl<'a> Iterator for PyTupleIterator<'a> {
}
}

impl<'a> DoubleEndedIterator for PyTupleIterator<'a> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.index < self.length {
let item = unsafe { self.get_item(self.length - 1) };
self.length -= 1;
Some(item)
} else {
None
}
}
}

impl<'a> ExactSizeIterator for PyTupleIterator<'a> {
fn len(&self) -> usize {
self.length.saturating_sub(self.index)
}
}

impl FusedIterator for PyTupleIterator<'_> {}

impl<'a> IntoIterator for &'a PyTuple {
type Item = &'a PyAny;
type IntoIter = PyTupleIterator<'a>;
Expand Down Expand Up @@ -510,6 +533,33 @@ mod tests {

assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
assert_eq!(iter.size_hint(), (0, Some(0)));

assert!(iter.next().is_none());
assert!(iter.next().is_none());
});
}

#[test]
fn test_iter_rev() {
Python::with_gil(|py| {
let ob = (1, 2, 3).to_object(py);
let tuple: &PyTuple = ob.downcast(py).unwrap();
assert_eq!(3, tuple.len());
let mut iter = tuple.iter().rev();

assert_eq!(iter.size_hint(), (3, Some(3)));

assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
assert_eq!(iter.size_hint(), (2, Some(2)));

assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
assert_eq!(iter.size_hint(), (1, Some(1)));

assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
assert_eq!(iter.size_hint(), (0, Some(0)));
adamreichold marked this conversation as resolved.
Show resolved Hide resolved

assert!(iter.next().is_none());
assert!(iter.next().is_none());
});
}

Expand Down