1use crate::ffi_ptr_ext::FfiPtrExt;
2use crate::instance::Borrowed;
3use crate::py_result_ext::PyResultExt;
4use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck};
5
6#[repr(transparent)]
31pub struct PyIterator(PyAny);
32pyobject_native_type_named!(PyIterator);
33
34impl PyIterator {
35 pub fn from_object<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyIterator>> {
40 unsafe {
41 ffi::PyObject_GetIter(obj.as_ptr())
42 .assume_owned_or_err(obj.py())
43 .downcast_into_unchecked()
44 }
45 }
46
47 #[deprecated(since = "0.23.0", note = "renamed to `PyIterator::from_object`")]
49 #[inline]
50 pub fn from_bound_object<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyIterator>> {
51 Self::from_object(obj)
52 }
53}
54
55impl<'py> Iterator for Bound<'py, PyIterator> {
56 type Item = PyResult<Bound<'py, PyAny>>;
57
58 #[inline]
65 fn next(&mut self) -> Option<Self::Item> {
66 Borrowed::from(&*self).next()
67 }
68
69 #[cfg(not(Py_LIMITED_API))]
70 fn size_hint(&self) -> (usize, Option<usize>) {
71 let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) };
72 (hint.max(0) as usize, None)
73 }
74}
75
76impl<'py> Borrowed<'_, 'py, PyIterator> {
77 fn next(self) -> Option<PyResult<Bound<'py, PyAny>>> {
80 let py = self.py();
81
82 match unsafe { ffi::PyIter_Next(self.as_ptr()).assume_owned_or_opt(py) } {
83 Some(obj) => Some(Ok(obj)),
84 None => PyErr::take(py).map(Err),
85 }
86 }
87}
88
89impl<'py> IntoIterator for &Bound<'py, PyIterator> {
90 type Item = PyResult<Bound<'py, PyAny>>;
91 type IntoIter = Bound<'py, PyIterator>;
92
93 fn into_iter(self) -> Self::IntoIter {
94 self.clone()
95 }
96}
97
98impl PyTypeCheck for PyIterator {
99 const NAME: &'static str = "Iterator";
100
101 fn type_check(object: &Bound<'_, PyAny>) -> bool {
102 unsafe { ffi::PyIter_Check(object.as_ptr()) != 0 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::PyIterator;
109 use crate::exceptions::PyTypeError;
110 use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
111 use crate::{ffi, IntoPyObject, Python};
112
113 #[test]
114 fn vec_iter() {
115 Python::with_gil(|py| {
116 let inst = vec![10, 20].into_pyobject(py).unwrap();
117 let mut it = inst.try_iter().unwrap();
118 assert_eq!(
119 10_i32,
120 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
121 );
122 assert_eq!(
123 20_i32,
124 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
125 );
126 assert!(it.next().is_none());
127 });
128 }
129
130 #[test]
131 fn iter_refcnt() {
132 let (obj, count) = Python::with_gil(|py| {
133 let obj = vec![10, 20].into_pyobject(py).unwrap();
134 let count = obj.get_refcnt();
135 (obj.unbind(), count)
136 });
137
138 Python::with_gil(|py| {
139 let inst = obj.bind(py);
140 let mut it = inst.try_iter().unwrap();
141
142 assert_eq!(
143 10_i32,
144 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
145 );
146 });
147
148 Python::with_gil(move |py| {
149 assert_eq!(count, obj.get_refcnt(py));
150 });
151 }
152
153 #[test]
154 fn iter_item_refcnt() {
155 Python::with_gil(|py| {
156 let count;
157 let obj = py.eval(ffi::c_str!("object()"), None, None).unwrap();
158 let list = {
159 let list = PyList::empty(py);
160 list.append(10).unwrap();
161 list.append(&obj).unwrap();
162 count = obj.get_refcnt();
163 list
164 };
165
166 {
167 let mut it = list.iter();
168
169 assert_eq!(10_i32, it.next().unwrap().extract::<'_, i32>().unwrap());
170 assert!(it.next().unwrap().is(&obj));
171 assert!(it.next().is_none());
172 }
173 assert_eq!(count, obj.get_refcnt());
174 });
175 }
176
177 #[test]
178 fn fibonacci_generator() {
179 let fibonacci_generator = ffi::c_str!(
180 r#"
181def fibonacci(target):
182 a = 1
183 b = 1
184 for _ in range(target):
185 yield a
186 a, b = b, a + b
187"#
188 );
189
190 Python::with_gil(|py| {
191 let context = PyDict::new(py);
192 py.run(fibonacci_generator, None, Some(&context)).unwrap();
193
194 let generator = py
195 .eval(ffi::c_str!("fibonacci(5)"), None, Some(&context))
196 .unwrap();
197 for (actual, expected) in generator.try_iter().unwrap().zip(&[1, 1, 2, 3, 5]) {
198 let actual = actual.unwrap().extract::<usize>().unwrap();
199 assert_eq!(actual, *expected)
200 }
201 });
202 }
203
204 #[test]
205 fn fibonacci_generator_bound() {
206 use crate::types::any::PyAnyMethods;
207 use crate::Bound;
208
209 let fibonacci_generator = ffi::c_str!(
210 r#"
211def fibonacci(target):
212 a = 1
213 b = 1
214 for _ in range(target):
215 yield a
216 a, b = b, a + b
217"#
218 );
219
220 Python::with_gil(|py| {
221 let context = PyDict::new(py);
222 py.run(fibonacci_generator, None, Some(&context)).unwrap();
223
224 let generator: Bound<'_, PyIterator> = py
225 .eval(ffi::c_str!("fibonacci(5)"), None, Some(&context))
226 .unwrap()
227 .downcast_into()
228 .unwrap();
229 let mut items = vec![];
230 for actual in &generator {
231 let actual = actual.unwrap().extract::<usize>().unwrap();
232 items.push(actual);
233 }
234 assert_eq!(items, [1, 1, 2, 3, 5]);
235 });
236 }
237
238 #[test]
239 fn int_not_iterable() {
240 Python::with_gil(|py| {
241 let x = 5i32.into_pyobject(py).unwrap();
242 let err = PyIterator::from_object(&x).unwrap_err();
243
244 assert!(err.is_instance_of::<PyTypeError>(py));
245 });
246 }
247
248 #[test]
249 #[cfg(feature = "macros")]
250 fn python_class_not_iterator() {
251 use crate::PyErr;
252
253 #[crate::pyclass(crate = "crate")]
254 struct Downcaster {
255 failed: Option<PyErr>,
256 }
257
258 #[crate::pymethods(crate = "crate")]
259 impl Downcaster {
260 fn downcast_iterator(&mut self, obj: &crate::Bound<'_, crate::PyAny>) {
261 self.failed = Some(obj.downcast::<PyIterator>().unwrap_err().into());
262 }
263 }
264
265 Python::with_gil(|py| {
267 let downcaster = crate::Py::new(py, Downcaster { failed: None }).unwrap();
268 crate::py_run!(
269 py,
270 downcaster,
271 r#"
272 from collections.abc import Sequence
273
274 class MySequence(Sequence):
275 def __init__(self):
276 self._data = [1, 2, 3]
277
278 def __getitem__(self, index):
279 return self._data[index]
280
281 def __len__(self):
282 return len(self._data)
283
284 downcaster.downcast_iterator(MySequence())
285 "#
286 );
287
288 assert_eq!(
289 downcaster.borrow_mut(py).failed.take().unwrap().to_string(),
290 "TypeError: 'MySequence' object cannot be converted to 'Iterator'"
291 );
292 });
293 }
294
295 #[test]
296 #[cfg(feature = "macros")]
297 fn python_class_iterator() {
298 #[crate::pyfunction(crate = "crate")]
299 fn assert_iterator(obj: &crate::Bound<'_, crate::PyAny>) {
300 assert!(obj.downcast::<PyIterator>().is_ok())
301 }
302
303 Python::with_gil(|py| {
305 let assert_iterator = crate::wrap_pyfunction!(assert_iterator, py).unwrap();
306 crate::py_run!(
307 py,
308 assert_iterator,
309 r#"
310 class MyIter:
311 def __next__(self):
312 raise StopIteration
313
314 assert_iterator(MyIter())
315 "#
316 );
317 });
318 }
319
320 #[test]
321 #[cfg(not(Py_LIMITED_API))]
322 fn length_hint_becomes_size_hint_lower_bound() {
323 Python::with_gil(|py| {
324 let list = py.eval(ffi::c_str!("[1, 2, 3]"), None, None).unwrap();
325 let iter = list.try_iter().unwrap();
326 let hint = iter.size_hint();
327 assert_eq!(hint, (3, None));
328 });
329 }
330}