Skip to content

Commit f519845

Browse files
Allow non-ExactSizeIterator in PyList::new (#5830)
* Allow non-`ExactSizeIterator` in `PyList::new` * Remove `ExactSizeIterator` & update docs * Add test
1 parent ee66c21 commit f519845

File tree

2 files changed

+56
-67
lines changed

2 files changed

+56
-67
lines changed

newsfragments/5830.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow non-`ExactSizeIterator` in `PyList::new`

src/types/list.rs

Lines changed: 55 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -36,49 +36,9 @@ impl crate::impl_::pyclass::PyClassBaseType for PyList {
3636
crate::impl_::pycell::PyVariableClassObject<T>;
3737
}
3838

39-
#[inline]
40-
#[track_caller]
41-
fn try_new_from_iter<'py>(
42-
py: Python<'py>,
43-
mut elements: impl ExactSizeIterator<Item = PyResult<Bound<'py, PyAny>>>,
44-
) -> PyResult<Bound<'py, PyList>> {
45-
unsafe {
46-
// PyList_New checks for overflow but has a bad error message, so we check ourselves
47-
let len: Py_ssize_t = elements
48-
.len()
49-
.try_into()
50-
.expect("out of range integral type conversion attempted on `elements.len()`");
51-
52-
let ptr = ffi::PyList_New(len);
53-
54-
// We create the `Bound` pointer here for two reasons:
55-
// - panics if the ptr is null
56-
// - its Drop cleans up the list if user code or the asserts panic.
57-
let list = ptr.assume_owned(py).cast_into_unchecked();
58-
59-
let count = (&mut elements)
60-
.take(len as usize)
61-
.try_fold(0, |count, item| {
62-
#[cfg(not(Py_LIMITED_API))]
63-
ffi::PyList_SET_ITEM(ptr, count, item?.into_ptr());
64-
#[cfg(Py_LIMITED_API)]
65-
ffi::PyList_SetItem(ptr, count, item?.into_ptr());
66-
Ok::<_, PyErr>(count + 1)
67-
})?;
68-
69-
assert!(elements.next().is_none(), "Attempted to create PyList but `elements` was larger than reported by its `ExactSizeIterator` implementation.");
70-
assert_eq!(len, count, "Attempted to create PyList but `elements` was smaller than reported by its `ExactSizeIterator` implementation.");
71-
72-
Ok(list)
73-
}
74-
}
75-
7639
impl PyList {
7740
/// Constructs a new list with the given elements.
7841
///
79-
/// If you want to create a [`PyList`] with elements of different or unknown types, or from an
80-
/// iterable that doesn't implement [`ExactSizeIterator`], use [`PyListMethods::append`].
81-
///
8242
/// # Examples
8343
///
8444
/// ```rust
@@ -97,20 +57,42 @@ impl PyList {
9757
///
9858
/// # Panics
9959
///
100-
/// This function will panic if `element`'s [`ExactSizeIterator`] implementation is incorrect.
60+
/// This function will panic if `element`'s [`Iterator::size_hint`] implementation is incorrect.
10161
/// All standard library structures implement this trait correctly, if they do, so calling this
10262
/// function with (for example) [`Vec`]`<T>` or `&[T]` will always succeed.
10363
#[track_caller]
104-
pub fn new<'py, T, U>(
64+
pub fn new<'py, T>(
10565
py: Python<'py>,
106-
elements: impl IntoIterator<Item = T, IntoIter = U>,
66+
elements: impl IntoIterator<Item = T>,
10767
) -> PyResult<Bound<'py, PyList>>
10868
where
10969
T: IntoPyObject<'py>,
110-
U: ExactSizeIterator<Item = T>,
11170
{
112-
let iter = elements.into_iter().map(|e| e.into_bound_py_any(py));
113-
try_new_from_iter(py, iter)
71+
let mut elements = elements.into_iter().map(|e| e.into_bound_py_any(py));
72+
let (min_len, _) = elements.size_hint();
73+
74+
// PyList_New checks for overflow but has a bad error message, so we check ourselves
75+
let len: Py_ssize_t = min_len
76+
.try_into()
77+
.expect("out of range integral type conversion attempted on `elements.len()`");
78+
79+
let list = unsafe { ffi::PyList_New(len).assume_owned(py).cast_into_unchecked() };
80+
81+
let count = (&mut elements)
82+
.take(len as usize)
83+
.try_fold(0, |count, item| unsafe {
84+
#[cfg(not(Py_LIMITED_API))]
85+
ffi::PyList_SET_ITEM(list.as_ptr(), count, item?.into_ptr());
86+
#[cfg(Py_LIMITED_API)]
87+
ffi::PyList_SetItem(list.as_ptr(), count, item?.into_ptr());
88+
Ok::<_, PyErr>(count + 1)
89+
})?;
90+
91+
assert_eq!(len, count, "Attempted to create PyList but `elements` was smaller than reported by its `size_hint` implementation.");
92+
93+
elements.try_for_each(|item| list.append(item?))?;
94+
95+
Ok(list)
11496
}
11597

11698
/// Constructs a new empty list.
@@ -1506,7 +1488,7 @@ mod tests {
15061488

15071489
use std::ops::Range;
15081490

1509-
// An iterator that lies about its `ExactSizeIterator` implementation.
1491+
// An iterator that lies about its `size_hint` implementation.
15101492
// See https://github.com/PyO3/pyo3/issues/2118
15111493
struct FaultyIter(Range<usize>, usize);
15121494

@@ -1516,28 +1498,15 @@ mod tests {
15161498
fn next(&mut self) -> Option<Self::Item> {
15171499
self.0.next()
15181500
}
1519-
}
15201501

1521-
impl ExactSizeIterator for FaultyIter {
1522-
fn len(&self) -> usize {
1523-
self.1
1502+
fn size_hint(&self) -> (usize, Option<usize>) {
1503+
(self.1, Some(self.1))
15241504
}
15251505
}
15261506

15271507
#[test]
15281508
#[should_panic(
1529-
expected = "Attempted to create PyList but `elements` was larger than reported by its `ExactSizeIterator` implementation."
1530-
)]
1531-
fn too_long_iterator() {
1532-
Python::attach(|py| {
1533-
let iter = FaultyIter(0..usize::MAX, 73);
1534-
let _list = PyList::new(py, iter).unwrap();
1535-
})
1536-
}
1537-
1538-
#[test]
1539-
#[should_panic(
1540-
expected = "Attempted to create PyList but `elements` was smaller than reported by its `ExactSizeIterator` implementation."
1509+
expected = "Attempted to create PyList but `elements` was smaller than reported by its `size_hint` implementation."
15411510
)]
15421511
fn too_short_iterator() {
15431512
Python::attach(|py| {
@@ -1597,11 +1566,9 @@ mod tests {
15971566
Bad(i)
15981567
})
15991568
}
1600-
}
16011569

1602-
impl ExactSizeIterator for FaultyIter {
1603-
fn len(&self) -> usize {
1604-
self.1
1570+
fn size_hint(&self) -> (usize, Option<usize>) {
1571+
(self.1, Some(self.1))
16051572
}
16061573
}
16071574

@@ -1798,4 +1765,25 @@ mod tests {
17981765
assert_eq!(list.iter().count(), 3);
17991766
})
18001767
}
1768+
1769+
#[test]
1770+
fn test_new_from_non_exact_iter() {
1771+
Python::attach(|py| {
1772+
let iter = (0..5)
1773+
.filter(|_| true) // Filter does not implement ExactSizeIterator
1774+
.map(|item| item.into_pyobject(py).unwrap());
1775+
1776+
assert!(
1777+
matches!(iter.size_hint(), (0, _)),
1778+
"size_hint lower bound should be 0 because we do not now the final size after filter"
1779+
);
1780+
1781+
let list = PyList::new(py, iter).unwrap();
1782+
assert_eq!(
1783+
list.len(),
1784+
5,
1785+
"list should contain all elements even though size_hint is 0"
1786+
);
1787+
})
1788+
}
18011789
}

0 commit comments

Comments
 (0)