1use crate::ffi_ptr_ext::FfiPtrExt;
2use crate::instance::Borrowed;
3use crate::py_result_ext::PyResultExt;
4use crate::sync::PyOnceLock;
5use crate::types::{PyType, PyTypeMethods};
6use crate::{ffi, Bound, Py, PyAny, PyErr, PyResult};
7
8#[repr(transparent)]
33pub struct PyIterator(PyAny);
34
35pyobject_native_type_core!(
36 PyIterator,
37 |py| {
38 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
39 TYPE.import(py, "collections.abc", "Iterator")
40 .unwrap()
41 .as_type_ptr()
42 },
43 "collections.abc",
44 "Iterator",
45 #module=Some("collections.abc"),
46 #checkfunction=ffi::PyIter_Check
47);
48
49impl PyIterator {
50 pub fn from_object<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyIterator>> {
55 unsafe {
56 ffi::PyObject_GetIter(obj.as_ptr())
57 .assume_owned_or_err(obj.py())
58 .cast_into_unchecked()
59 }
60 }
61}
62
63#[derive(Debug)]
65#[cfg(all(not(PyPy), Py_3_10))]
66pub enum PySendResult<'py> {
67 Next(Bound<'py, PyAny>),
69 Return(Bound<'py, PyAny>),
71}
72
73#[cfg(all(not(PyPy), Py_3_10))]
74impl<'py> Bound<'py, PyIterator> {
75 #[inline]
82 pub fn send(&self, value: &Bound<'py, PyAny>) -> PyResult<PySendResult<'py>> {
83 let py = self.py();
84 let mut result = std::ptr::null_mut();
85 match unsafe { ffi::PyIter_Send(self.as_ptr(), value.as_ptr(), &mut result) } {
86 ffi::PySendResult::PYGEN_ERROR => Err(PyErr::fetch(py)),
87 ffi::PySendResult::PYGEN_RETURN => Ok(PySendResult::Return(unsafe {
88 result.assume_owned_unchecked(py)
89 })),
90 ffi::PySendResult::PYGEN_NEXT => Ok(PySendResult::Next(unsafe {
91 result.assume_owned_unchecked(py)
92 })),
93 }
94 }
95}
96
97impl<'py> Iterator for Bound<'py, PyIterator> {
98 type Item = PyResult<Bound<'py, PyAny>>;
99
100 #[inline]
107 fn next(&mut self) -> Option<Self::Item> {
108 Borrowed::from(&*self).next()
109 }
110
111 #[cfg(not(Py_LIMITED_API))]
112 fn size_hint(&self) -> (usize, Option<usize>) {
113 let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) };
115 if hint < 0 {
116 let py = self.py();
117 PyErr::fetch(py).write_unraisable(py, Some(self));
118 (0, None)
119 } else {
120 (hint as usize, None)
121 }
122 }
123}
124
125impl<'py> Borrowed<'_, 'py, PyIterator> {
126 fn next(self) -> Option<PyResult<Bound<'py, PyAny>>> {
129 let py = self.py();
130
131 match unsafe { ffi::PyIter_Next(self.as_ptr()).assume_owned_or_opt(py) } {
132 Some(obj) => Some(Ok(obj)),
133 None => PyErr::take(py).map(Err),
134 }
135 }
136}
137
138impl<'py> IntoIterator for &Bound<'py, PyIterator> {
139 type Item = PyResult<Bound<'py, PyAny>>;
140 type IntoIter = Bound<'py, PyIterator>;
141
142 fn into_iter(self) -> Self::IntoIter {
143 self.clone()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::PyIterator;
150 #[cfg(all(not(PyPy), Py_3_10))]
151 use super::PySendResult;
152 use crate::exceptions::PyTypeError;
153 #[cfg(all(not(PyPy), Py_3_10))]
154 use crate::types::PyNone;
155 use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
156 #[cfg(all(feature = "macros", Py_3_8, not(Py_LIMITED_API)))]
157 use crate::PyErr;
158 use crate::{IntoPyObject, PyTypeInfo, Python};
159
160 #[test]
161 fn vec_iter() {
162 Python::attach(|py| {
163 let inst = vec![10, 20].into_pyobject(py).unwrap();
164 let mut it = inst.try_iter().unwrap();
165 assert_eq!(
166 10_i32,
167 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
168 );
169 assert_eq!(
170 20_i32,
171 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
172 );
173 assert!(it.next().is_none());
174 });
175 }
176
177 #[test]
178 fn iter_refcnt() {
179 let (obj, count) = Python::attach(|py| {
180 let obj = vec![10, 20].into_pyobject(py).unwrap();
181 let count = obj.get_refcnt();
182 (obj.unbind(), count)
183 });
184
185 Python::attach(|py| {
186 let inst = obj.bind(py);
187 let mut it = inst.try_iter().unwrap();
188
189 assert_eq!(
190 10_i32,
191 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
192 );
193 });
194
195 Python::attach(move |py| {
196 assert_eq!(count, obj.get_refcnt(py));
197 });
198 }
199
200 #[test]
201 fn iter_item_refcnt() {
202 Python::attach(|py| {
203 let count;
204 let obj = py.eval(c"object()", None, None).unwrap();
205 let list = {
206 let list = PyList::empty(py);
207 list.append(10).unwrap();
208 list.append(&obj).unwrap();
209 count = obj.get_refcnt();
210 list
211 };
212
213 {
214 let mut it = list.iter();
215
216 assert_eq!(10_i32, it.next().unwrap().extract::<'_, i32>().unwrap());
217 assert!(it.next().unwrap().is(&obj));
218 assert!(it.next().is_none());
219 }
220 assert_eq!(count, obj.get_refcnt());
221 });
222 }
223
224 #[test]
225 fn fibonacci_generator() {
226 let fibonacci_generator = cr#"
227def fibonacci(target):
228 a = 1
229 b = 1
230 for _ in range(target):
231 yield a
232 a, b = b, a + b
233"#;
234
235 Python::attach(|py| {
236 let context = PyDict::new(py);
237 py.run(fibonacci_generator, None, Some(&context)).unwrap();
238
239 let generator = py.eval(c"fibonacci(5)", None, Some(&context)).unwrap();
240 for (actual, expected) in generator.try_iter().unwrap().zip(&[1, 1, 2, 3, 5]) {
241 let actual = actual.unwrap().extract::<usize>().unwrap();
242 assert_eq!(actual, *expected)
243 }
244 });
245 }
246
247 #[test]
248 #[cfg(all(not(PyPy), Py_3_10))]
249 fn send_generator() {
250 let generator = cr#"
251def gen():
252 value = None
253 while(True):
254 value = yield value
255 if value is None:
256 return
257"#;
258
259 Python::attach(|py| {
260 let context = PyDict::new(py);
261 py.run(generator, None, Some(&context)).unwrap();
262
263 let generator = py.eval(c"gen()", None, Some(&context)).unwrap();
264
265 let one = 1i32.into_pyobject(py).unwrap();
266 assert!(matches!(
267 generator.try_iter().unwrap().send(&PyNone::get(py)).unwrap(),
268 PySendResult::Next(value) if value.is_none()
269 ));
270 assert!(matches!(
271 generator.try_iter().unwrap().send(&one).unwrap(),
272 PySendResult::Next(value) if value.is(&one)
273 ));
274 assert!(matches!(
275 generator.try_iter().unwrap().send(&PyNone::get(py)).unwrap(),
276 PySendResult::Return(value) if value.is_none()
277 ));
278 });
279 }
280
281 #[test]
282 fn fibonacci_generator_bound() {
283 use crate::types::any::PyAnyMethods;
284 use crate::Bound;
285
286 let fibonacci_generator = cr#"
287def fibonacci(target):
288 a = 1
289 b = 1
290 for _ in range(target):
291 yield a
292 a, b = b, a + b
293"#;
294
295 Python::attach(|py| {
296 let context = PyDict::new(py);
297 py.run(fibonacci_generator, None, Some(&context)).unwrap();
298
299 let generator: Bound<'_, PyIterator> = py
300 .eval(c"fibonacci(5)", None, Some(&context))
301 .unwrap()
302 .cast_into()
303 .unwrap();
304 let mut items = vec![];
305 for actual in &generator {
306 let actual = actual.unwrap().extract::<usize>().unwrap();
307 items.push(actual);
308 }
309 assert_eq!(items, [1, 1, 2, 3, 5]);
310 });
311 }
312
313 #[test]
314 fn int_not_iterable() {
315 Python::attach(|py| {
316 let x = 5i32.into_pyobject(py).unwrap();
317 let err = PyIterator::from_object(&x).unwrap_err();
318
319 assert!(err.is_instance_of::<PyTypeError>(py));
320 });
321 }
322
323 #[test]
324 #[cfg(feature = "macros")]
325 fn python_class_not_iterator() {
326 use crate::PyErr;
327
328 #[crate::pyclass(crate = "crate")]
329 struct Downcaster {
330 failed: Option<PyErr>,
331 }
332
333 #[crate::pymethods(crate = "crate")]
334 impl Downcaster {
335 fn downcast_iterator(&mut self, obj: &crate::Bound<'_, crate::PyAny>) {
336 self.failed = Some(obj.cast::<PyIterator>().unwrap_err().into());
337 }
338 }
339
340 Python::attach(|py| {
342 let downcaster = crate::Py::new(py, Downcaster { failed: None }).unwrap();
343 crate::py_run!(
344 py,
345 downcaster,
346 r#"
347 from collections.abc import Sequence
348
349 class MySequence(Sequence):
350 def __init__(self):
351 self._data = [1, 2, 3]
352
353 def __getitem__(self, index):
354 return self._data[index]
355
356 def __len__(self):
357 return len(self._data)
358
359 downcaster.downcast_iterator(MySequence())
360 "#
361 );
362
363 assert_eq!(
364 downcaster.borrow_mut(py).failed.take().unwrap().to_string(),
365 "TypeError: 'MySequence' object cannot be cast as 'Iterator'"
366 );
367 });
368 }
369
370 #[test]
371 #[cfg(feature = "macros")]
372 fn python_class_iterator() {
373 #[crate::pyfunction(crate = "crate")]
374 fn assert_iterator(obj: &crate::Bound<'_, crate::PyAny>) {
375 assert!(obj.cast::<PyIterator>().is_ok())
376 }
377
378 Python::attach(|py| {
380 let assert_iterator = crate::wrap_pyfunction!(assert_iterator, py).unwrap();
381 crate::py_run!(
382 py,
383 assert_iterator,
384 r#"
385 class MyIter:
386 def __next__(self):
387 raise StopIteration
388
389 assert_iterator(MyIter())
390 "#
391 );
392 });
393 }
394
395 #[test]
396 #[cfg(not(Py_LIMITED_API))]
397 fn length_hint_becomes_size_hint_lower_bound() {
398 Python::attach(|py| {
399 let list = py.eval(c"[1, 2, 3]", None, None).unwrap();
400 let iter = list.try_iter().unwrap();
401 let hint = iter.size_hint();
402 assert_eq!(hint, (3, None));
403 });
404 }
405
406 #[test]
407 #[cfg(all(feature = "macros", Py_3_8, not(Py_LIMITED_API)))]
408 fn length_hint_error() {
409 #[crate::pyfunction(crate = "crate")]
410 fn test_size_hint(obj: &crate::Bound<'_, crate::PyAny>, should_error: bool) {
411 let iter = obj.cast::<PyIterator>().unwrap();
412 crate::test_utils::UnraisableCapture::enter(obj.py(), |capture| {
413 assert_eq!((0, None), iter.size_hint());
414 assert_eq!(should_error, capture.take_capture().is_some());
415 });
416 assert!(PyErr::take(obj.py()).is_none());
417 }
418
419 Python::attach(|py| {
420 let test_size_hint = crate::wrap_pyfunction!(test_size_hint, py).unwrap();
421 crate::py_run!(
422 py,
423 test_size_hint,
424 r#"
425 class NoHintIter:
426 def __next__(self):
427 raise StopIteration
428
429 def __length_hint__(self):
430 return NotImplemented
431
432 class ErrorHintIter:
433 def __next__(self):
434 raise StopIteration
435
436 def __length_hint__(self):
437 raise ValueError("bad hint impl")
438
439 test_size_hint(NoHintIter(), False)
440 test_size_hint(ErrorHintIter(), True)
441 "#
442 );
443 });
444 }
445
446 #[test]
447 fn test_type_object() {
448 Python::attach(|py| {
449 let abc = PyIterator::type_object(py);
450 let iter = py.eval(c"iter(())", None, None).unwrap();
451 assert!(iter.is_instance(&abc).unwrap());
452 })
453 }
454}