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#[cfg(feature = "gil-refs")]
6use crate::{AsPyPointer, PyDowncastError, PyNativeType};
7
8#[repr(transparent)]
32pub struct PyIterator(PyAny);
33pyobject_native_type_named!(PyIterator);
34pyobject_native_type_extract!(PyIterator);
35
36impl PyIterator {
37 #[cfg(feature = "gil-refs")]
39 #[deprecated(
40 since = "0.21.0",
41 note = "`PyIterator::from_object` will be replaced by `PyIterator::from_bound_object` in a future PyO3 version"
42 )]
43 pub fn from_object(obj: &PyAny) -> PyResult<&PyIterator> {
44 Self::from_bound_object(&obj.as_borrowed()).map(Bound::into_gil_ref)
45 }
46
47 pub fn from_bound_object<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyIterator>> {
52 unsafe {
53 ffi::PyObject_GetIter(obj.as_ptr())
54 .assume_owned_or_err(obj.py())
55 .downcast_into_unchecked()
56 }
57 }
58}
59
60#[cfg(feature = "gil-refs")]
61impl<'p> Iterator for &'p PyIterator {
62 type Item = PyResult<&'p PyAny>;
63
64 fn next(&mut self) -> Option<Self::Item> {
71 self.as_borrowed()
72 .next()
73 .map(|result| result.map(Bound::into_gil_ref))
74 }
75
76 #[cfg(not(Py_LIMITED_API))]
77 fn size_hint(&self) -> (usize, Option<usize>) {
78 self.as_borrowed().size_hint()
79 }
80}
81
82impl<'py> Iterator for Bound<'py, PyIterator> {
83 type Item = PyResult<Bound<'py, PyAny>>;
84
85 #[inline]
92 fn next(&mut self) -> Option<Self::Item> {
93 Borrowed::from(&*self).next()
94 }
95
96 #[cfg(not(Py_LIMITED_API))]
97 fn size_hint(&self) -> (usize, Option<usize>) {
98 let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) };
99 (hint.max(0) as usize, None)
100 }
101}
102
103impl<'py> Borrowed<'_, 'py, PyIterator> {
104 fn next(self) -> Option<PyResult<Bound<'py, PyAny>>> {
107 let py = self.py();
108
109 match unsafe { ffi::PyIter_Next(self.as_ptr()).assume_owned_or_opt(py) } {
110 Some(obj) => Some(Ok(obj)),
111 None => PyErr::take(py).map(Err),
112 }
113 }
114}
115
116impl<'py> IntoIterator for &Bound<'py, PyIterator> {
117 type Item = PyResult<Bound<'py, PyAny>>;
118 type IntoIter = Bound<'py, PyIterator>;
119
120 fn into_iter(self) -> Self::IntoIter {
121 self.clone()
122 }
123}
124
125impl PyTypeCheck for PyIterator {
126 const NAME: &'static str = "Iterator";
127
128 fn type_check(object: &Bound<'_, PyAny>) -> bool {
129 unsafe { ffi::PyIter_Check(object.as_ptr()) != 0 }
130 }
131}
132
133#[cfg(feature = "gil-refs")]
134#[allow(deprecated)]
135impl<'v> crate::PyTryFrom<'v> for PyIterator {
136 fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyIterator, PyDowncastError<'v>> {
137 let value = value.into();
138 unsafe {
139 if ffi::PyIter_Check(value.as_ptr()) != 0 {
140 Ok(value.downcast_unchecked())
141 } else {
142 Err(PyDowncastError::new(value, "Iterator"))
143 }
144 }
145 }
146
147 fn try_from_exact<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyIterator, PyDowncastError<'v>> {
148 value.into().downcast()
149 }
150
151 #[inline]
152 unsafe fn try_from_unchecked<V: Into<&'v PyAny>>(value: V) -> &'v PyIterator {
153 let ptr = value.into() as *const _ as *const PyIterator;
154 &*ptr
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::PyIterator;
161 use crate::exceptions::PyTypeError;
162 use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
163 use crate::{Python, ToPyObject};
164
165 #[test]
166 fn vec_iter() {
167 Python::with_gil(|py| {
168 let obj = vec![10, 20].to_object(py);
169 let inst = obj.bind(py);
170 let mut it = inst.iter().unwrap();
171 assert_eq!(
172 10_i32,
173 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
174 );
175 assert_eq!(
176 20_i32,
177 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
178 );
179 assert!(it.next().is_none());
180 });
181 }
182
183 #[test]
184 fn iter_refcnt() {
185 let (obj, count) = Python::with_gil(|py| {
186 let obj = vec![10, 20].to_object(py);
187 let count = obj.get_refcnt(py);
188 (obj, count)
189 });
190
191 Python::with_gil(|py| {
192 let inst = obj.bind(py);
193 let mut it = inst.iter().unwrap();
194
195 assert_eq!(
196 10_i32,
197 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
198 );
199 });
200
201 Python::with_gil(move |py| {
202 assert_eq!(count, obj.get_refcnt(py));
203 });
204 }
205
206 #[test]
207 fn iter_item_refcnt() {
208 Python::with_gil(|py| {
209 let count;
210 let obj = py.eval_bound("object()", None, None).unwrap();
211 let list = {
212 let list = PyList::empty_bound(py);
213 list.append(10).unwrap();
214 list.append(&obj).unwrap();
215 count = obj.get_refcnt();
216 list.to_object(py)
217 };
218
219 {
220 let inst = list.bind(py);
221 let mut it = inst.iter().unwrap();
222
223 assert_eq!(
224 10_i32,
225 it.next().unwrap().unwrap().extract::<'_, i32>().unwrap()
226 );
227 assert!(it.next().unwrap().unwrap().is(&obj));
228 assert!(it.next().is_none());
229 }
230 assert_eq!(count, obj.get_refcnt());
231 });
232 }
233
234 #[test]
235 fn fibonacci_generator() {
236 let fibonacci_generator = r#"
237def fibonacci(target):
238 a = 1
239 b = 1
240 for _ in range(target):
241 yield a
242 a, b = b, a + b
243"#;
244
245 Python::with_gil(|py| {
246 let context = PyDict::new_bound(py);
247 py.run_bound(fibonacci_generator, None, Some(&context))
248 .unwrap();
249
250 let generator = py.eval_bound("fibonacci(5)", None, Some(&context)).unwrap();
251 for (actual, expected) in generator.iter().unwrap().zip(&[1, 1, 2, 3, 5]) {
252 let actual = actual.unwrap().extract::<usize>().unwrap();
253 assert_eq!(actual, *expected)
254 }
255 });
256 }
257
258 #[test]
259 fn fibonacci_generator_bound() {
260 use crate::types::any::PyAnyMethods;
261 use crate::Bound;
262
263 let fibonacci_generator = r#"
264def fibonacci(target):
265 a = 1
266 b = 1
267 for _ in range(target):
268 yield a
269 a, b = b, a + b
270"#;
271
272 Python::with_gil(|py| {
273 let context = PyDict::new_bound(py);
274 py.run_bound(fibonacci_generator, None, Some(&context))
275 .unwrap();
276
277 let generator: Bound<'_, PyIterator> = py
278 .eval_bound("fibonacci(5)", None, Some(&context))
279 .unwrap()
280 .downcast_into()
281 .unwrap();
282 let mut items = vec![];
283 for actual in &generator {
284 let actual = actual.unwrap().extract::<usize>().unwrap();
285 items.push(actual);
286 }
287 assert_eq!(items, [1, 1, 2, 3, 5]);
288 });
289 }
290
291 #[test]
292 fn int_not_iterable() {
293 Python::with_gil(|py| {
294 let x = 5.to_object(py);
295 let err = PyIterator::from_bound_object(x.bind(py)).unwrap_err();
296
297 assert!(err.is_instance_of::<PyTypeError>(py));
298 });
299 }
300
301 #[test]
302 #[cfg(feature = "gil-refs")]
303 #[allow(deprecated)]
304 fn iterator_try_from() {
305 Python::with_gil(|py| {
306 let obj: crate::Py<crate::PyAny> =
307 vec![10, 20].to_object(py).as_ref(py).iter().unwrap().into();
308 let iter = <PyIterator as crate::PyTryFrom>::try_from(obj.as_ref(py)).unwrap();
309 assert!(obj.is(iter));
310 });
311 }
312
313 #[test]
314 #[cfg(feature = "macros")]
315 fn python_class_not_iterator() {
316 use crate::PyErr;
317
318 #[crate::pyclass(crate = "crate")]
319 struct Downcaster {
320 failed: Option<PyErr>,
321 }
322
323 #[crate::pymethods(crate = "crate")]
324 impl Downcaster {
325 fn downcast_iterator(&mut self, obj: &crate::Bound<'_, crate::PyAny>) {
326 self.failed = Some(obj.downcast::<PyIterator>().unwrap_err().into());
327 }
328 }
329
330 Python::with_gil(|py| {
332 let downcaster = crate::Py::new(py, Downcaster { failed: None }).unwrap();
333 crate::py_run!(
334 py,
335 downcaster,
336 r#"
337 from collections.abc import Sequence
338
339 class MySequence(Sequence):
340 def __init__(self):
341 self._data = [1, 2, 3]
342
343 def __getitem__(self, index):
344 return self._data[index]
345
346 def __len__(self):
347 return len(self._data)
348
349 downcaster.downcast_iterator(MySequence())
350 "#
351 );
352
353 assert_eq!(
354 downcaster.borrow_mut(py).failed.take().unwrap().to_string(),
355 "TypeError: 'MySequence' object cannot be converted to 'Iterator'"
356 );
357 });
358 }
359
360 #[test]
361 #[cfg(feature = "macros")]
362 fn python_class_iterator() {
363 #[crate::pyfunction(crate = "crate")]
364 fn assert_iterator(obj: &crate::Bound<'_, crate::PyAny>) {
365 assert!(obj.downcast::<PyIterator>().is_ok())
366 }
367
368 Python::with_gil(|py| {
370 let assert_iterator = crate::wrap_pyfunction_bound!(assert_iterator, py).unwrap();
371 crate::py_run!(
372 py,
373 assert_iterator,
374 r#"
375 class MyIter:
376 def __next__(self):
377 raise StopIteration
378
379 assert_iterator(MyIter())
380 "#
381 );
382 });
383 }
384
385 #[test]
386 #[cfg(not(Py_LIMITED_API))]
387 fn length_hint_becomes_size_hint_lower_bound() {
388 Python::with_gil(|py| {
389 let list = py.eval_bound("[1, 2, 3]", None, None).unwrap();
390 let iter = list.iter().unwrap();
391 let hint = iter.size_hint();
392 assert_eq!(hint, (3, None));
393 });
394 }
395}