1 | use crate::ffi_ptr_ext::FfiPtrExt; |
2 | use crate::instance::Borrowed; |
3 | use crate::py_result_ext::PyResultExt; |
4 | use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck}; |
5 | |
6 | /// A Python iterator object. |
7 | /// |
8 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
9 | /// [`Py<PyIterator>`][crate::Py] or [`Bound<'py, PyIterator>`][Bound]. |
10 | /// |
11 | /// # Examples |
12 | /// |
13 | /// ```rust |
14 | /// use pyo3::prelude::*; |
15 | /// use pyo3::ffi::c_str; |
16 | /// |
17 | /// # fn main() -> PyResult<()> { |
18 | /// Python::with_gil(|py| -> PyResult<()> { |
19 | /// let list = py.eval(c_str!("iter([1, 2, 3, 4])" ), None, None)?; |
20 | /// let numbers: PyResult<Vec<usize>> = list |
21 | /// .try_iter()? |
22 | /// .map(|i| i.and_then(|i|i.extract::<usize>())) |
23 | /// .collect(); |
24 | /// let sum: usize = numbers?.iter().sum(); |
25 | /// assert_eq!(sum, 10); |
26 | /// Ok(()) |
27 | /// }) |
28 | /// # } |
29 | /// ``` |
30 | #[repr (transparent)] |
31 | pub struct PyIterator(PyAny); |
32 | pyobject_native_type_named!(PyIterator); |
33 | |
34 | impl PyIterator { |
35 | /// Builds an iterator for an iterable Python object; the equivalent of calling `iter(obj)` in Python. |
36 | /// |
37 | /// Usually it is more convenient to write [`obj.iter()`][crate::types::any::PyAnyMethods::iter], |
38 | /// which is a more concise way of calling this function. |
39 | pub fn from_object<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyIterator>> { |
40 | unsafe { |
41 | ffiResult, …>::PyObject_GetIter(arg1:obj.as_ptr()) |
42 | .assume_owned_or_err(obj.py()) |
43 | .downcast_into_unchecked() |
44 | } |
45 | } |
46 | |
47 | /// Deprecated name for [`PyIterator::from_object`]. |
48 | #[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 | |
55 | #[derive (Debug)] |
56 | #[cfg (all(not(PyPy), Py_3_10))] |
57 | pub enum PySendResult<'py> { |
58 | Next(Bound<'py, PyAny>), |
59 | Return(Bound<'py, PyAny>), |
60 | } |
61 | |
62 | #[cfg (all(not(PyPy), Py_3_10))] |
63 | impl<'py> Bound<'py, PyIterator> { |
64 | /// Sends a value into a python generator. This is the equivalent of calling `generator.send(value)` in Python. |
65 | /// This resumes the generator and continues its execution until the next `yield` or `return` statement. |
66 | /// If the generator exits without returning a value, this function returns a `StopException`. |
67 | /// The first call to `send` must be made with `None` as the argument to start the generator, failing to do so will raise a `TypeError`. |
68 | #[inline ] |
69 | pub fn send(&self, value: &Bound<'py, PyAny>) -> PyResult<PySendResult<'py>> { |
70 | let py: Python<'_> = self.py(); |
71 | let mut result: *mut PyObject = std::ptr::null_mut(); |
72 | match unsafe { ffi::PyIter_Send(self.as_ptr(), arg:value.as_ptr(), &mut result) } { |
73 | ffi::PySendResult::PYGEN_ERROR => Err(PyErr::fetch(py)), |
74 | ffi::PySendResult::PYGEN_RETURN => Ok(PySendResult::Return(unsafe { |
75 | result.assume_owned_unchecked(py) |
76 | })), |
77 | ffi::PySendResult::PYGEN_NEXT => Ok(PySendResult::Next(unsafe { |
78 | result.assume_owned_unchecked(py) |
79 | })), |
80 | } |
81 | } |
82 | } |
83 | |
84 | impl<'py> Iterator for Bound<'py, PyIterator> { |
85 | type Item = PyResult<Bound<'py, PyAny>>; |
86 | |
87 | /// Retrieves the next item from an iterator. |
88 | /// |
89 | /// Returns `None` when the iterator is exhausted. |
90 | /// If an exception occurs, returns `Some(Err(..))`. |
91 | /// Further `next()` calls after an exception occurs are likely |
92 | /// to repeatedly result in the same exception. |
93 | #[inline ] |
94 | fn next(&mut self) -> Option<Self::Item> { |
95 | Borrowed::from(&*self).next() |
96 | } |
97 | |
98 | #[cfg (not(Py_LIMITED_API))] |
99 | fn size_hint(&self) -> (usize, Option<usize>) { |
100 | let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) }; |
101 | (hint.max(0) as usize, None) |
102 | } |
103 | } |
104 | |
105 | impl<'py> Borrowed<'_, 'py, PyIterator> { |
106 | // TODO: this method is on Borrowed so that &'py PyIterator can use this; once that |
107 | // implementation is deleted this method should be moved to the `Bound<'py, PyIterator> impl |
108 | fn next(self) -> Option<PyResult<Bound<'py, PyAny>>> { |
109 | let py: Python<'_> = self.py(); |
110 | |
111 | match unsafe { ffi::PyIter_Next(self.as_ptr()).assume_owned_or_opt(py) } { |
112 | Some(obj: Bound<'_, PyAny>) => Some(Ok(obj)), |
113 | None => PyErr::take(py).map(Err), |
114 | } |
115 | } |
116 | } |
117 | |
118 | impl<'py> IntoIterator for &Bound<'py, PyIterator> { |
119 | type Item = PyResult<Bound<'py, PyAny>>; |
120 | type IntoIter = Bound<'py, PyIterator>; |
121 | |
122 | fn into_iter(self) -> Self::IntoIter { |
123 | self.clone() |
124 | } |
125 | } |
126 | |
127 | impl PyTypeCheck for PyIterator { |
128 | const NAME: &'static str = "Iterator" ; |
129 | |
130 | fn type_check(object: &Bound<'_, PyAny>) -> bool { |
131 | unsafe { ffi::PyIter_Check(obj:object.as_ptr()) != 0 } |
132 | } |
133 | } |
134 | |
135 | #[cfg (test)] |
136 | mod tests { |
137 | use super::PyIterator; |
138 | #[cfg (all(not(PyPy), Py_3_10))] |
139 | use super::PySendResult; |
140 | use crate::exceptions::PyTypeError; |
141 | #[cfg (all(not(PyPy), Py_3_10))] |
142 | use crate::types::PyNone; |
143 | use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods}; |
144 | use crate::{ffi, IntoPyObject, Python}; |
145 | |
146 | #[test ] |
147 | fn vec_iter() { |
148 | Python::with_gil(|py| { |
149 | let inst = vec![10, 20].into_pyobject(py).unwrap(); |
150 | let mut it = inst.try_iter().unwrap(); |
151 | assert_eq!( |
152 | 10_i32, |
153 | it.next().unwrap().unwrap().extract::<'_, i32>().unwrap() |
154 | ); |
155 | assert_eq!( |
156 | 20_i32, |
157 | it.next().unwrap().unwrap().extract::<'_, i32>().unwrap() |
158 | ); |
159 | assert!(it.next().is_none()); |
160 | }); |
161 | } |
162 | |
163 | #[test ] |
164 | fn iter_refcnt() { |
165 | let (obj, count) = Python::with_gil(|py| { |
166 | let obj = vec![10, 20].into_pyobject(py).unwrap(); |
167 | let count = obj.get_refcnt(); |
168 | (obj.unbind(), count) |
169 | }); |
170 | |
171 | Python::with_gil(|py| { |
172 | let inst = obj.bind(py); |
173 | let mut it = inst.try_iter().unwrap(); |
174 | |
175 | assert_eq!( |
176 | 10_i32, |
177 | it.next().unwrap().unwrap().extract::<'_, i32>().unwrap() |
178 | ); |
179 | }); |
180 | |
181 | Python::with_gil(move |py| { |
182 | assert_eq!(count, obj.get_refcnt(py)); |
183 | }); |
184 | } |
185 | |
186 | #[test ] |
187 | fn iter_item_refcnt() { |
188 | Python::with_gil(|py| { |
189 | let count; |
190 | let obj = py.eval(ffi::c_str!("object()" ), None, None).unwrap(); |
191 | let list = { |
192 | let list = PyList::empty(py); |
193 | list.append(10).unwrap(); |
194 | list.append(&obj).unwrap(); |
195 | count = obj.get_refcnt(); |
196 | list |
197 | }; |
198 | |
199 | { |
200 | let mut it = list.iter(); |
201 | |
202 | assert_eq!(10_i32, it.next().unwrap().extract::<'_, i32>().unwrap()); |
203 | assert!(it.next().unwrap().is(&obj)); |
204 | assert!(it.next().is_none()); |
205 | } |
206 | assert_eq!(count, obj.get_refcnt()); |
207 | }); |
208 | } |
209 | |
210 | #[test ] |
211 | fn fibonacci_generator() { |
212 | let fibonacci_generator = ffi::c_str!( |
213 | r#" |
214 | def fibonacci(target): |
215 | a = 1 |
216 | b = 1 |
217 | for _ in range(target): |
218 | yield a |
219 | a, b = b, a + b |
220 | "# |
221 | ); |
222 | |
223 | Python::with_gil(|py| { |
224 | let context = PyDict::new(py); |
225 | py.run(fibonacci_generator, None, Some(&context)).unwrap(); |
226 | |
227 | let generator = py |
228 | .eval(ffi::c_str!("fibonacci(5)" ), None, Some(&context)) |
229 | .unwrap(); |
230 | for (actual, expected) in generator.try_iter().unwrap().zip(&[1, 1, 2, 3, 5]) { |
231 | let actual = actual.unwrap().extract::<usize>().unwrap(); |
232 | assert_eq!(actual, *expected) |
233 | } |
234 | }); |
235 | } |
236 | |
237 | #[test ] |
238 | #[cfg (all(not(PyPy), Py_3_10))] |
239 | fn send_generator() { |
240 | let generator = ffi::c_str!( |
241 | r#" |
242 | def gen(): |
243 | value = None |
244 | while(True): |
245 | value = yield value |
246 | if value is None: |
247 | return |
248 | "# |
249 | ); |
250 | |
251 | Python::with_gil(|py| { |
252 | let context = PyDict::new(py); |
253 | py.run(generator, None, Some(&context)).unwrap(); |
254 | |
255 | let generator = py.eval(ffi::c_str!("gen()" ), None, Some(&context)).unwrap(); |
256 | |
257 | let one = 1i32.into_pyobject(py).unwrap(); |
258 | assert!(matches!( |
259 | generator.try_iter().unwrap().send(&PyNone::get(py)).unwrap(), |
260 | PySendResult::Next(value) if value.is_none() |
261 | )); |
262 | assert!(matches!( |
263 | generator.try_iter().unwrap().send(&one).unwrap(), |
264 | PySendResult::Next(value) if value.is(&one) |
265 | )); |
266 | assert!(matches!( |
267 | generator.try_iter().unwrap().send(&PyNone::get(py)).unwrap(), |
268 | PySendResult::Return(value) if value.is_none() |
269 | )); |
270 | }); |
271 | } |
272 | |
273 | #[test ] |
274 | fn fibonacci_generator_bound() { |
275 | use crate::types::any::PyAnyMethods; |
276 | use crate::Bound; |
277 | |
278 | let fibonacci_generator = ffi::c_str!( |
279 | r#" |
280 | def fibonacci(target): |
281 | a = 1 |
282 | b = 1 |
283 | for _ in range(target): |
284 | yield a |
285 | a, b = b, a + b |
286 | "# |
287 | ); |
288 | |
289 | Python::with_gil(|py| { |
290 | let context = PyDict::new(py); |
291 | py.run(fibonacci_generator, None, Some(&context)).unwrap(); |
292 | |
293 | let generator: Bound<'_, PyIterator> = py |
294 | .eval(ffi::c_str!("fibonacci(5)" ), None, Some(&context)) |
295 | .unwrap() |
296 | .downcast_into() |
297 | .unwrap(); |
298 | let mut items = vec![]; |
299 | for actual in &generator { |
300 | let actual = actual.unwrap().extract::<usize>().unwrap(); |
301 | items.push(actual); |
302 | } |
303 | assert_eq!(items, [1, 1, 2, 3, 5]); |
304 | }); |
305 | } |
306 | |
307 | #[test ] |
308 | fn int_not_iterable() { |
309 | Python::with_gil(|py| { |
310 | let x = 5i32.into_pyobject(py).unwrap(); |
311 | let err = PyIterator::from_object(&x).unwrap_err(); |
312 | |
313 | assert!(err.is_instance_of::<PyTypeError>(py)); |
314 | }); |
315 | } |
316 | |
317 | #[test ] |
318 | #[cfg (feature = "macros" )] |
319 | fn python_class_not_iterator() { |
320 | use crate::PyErr; |
321 | |
322 | #[crate::pyclass (crate = "crate" )] |
323 | struct Downcaster { |
324 | failed: Option<PyErr>, |
325 | } |
326 | |
327 | #[crate::pymethods (crate = "crate" )] |
328 | impl Downcaster { |
329 | fn downcast_iterator(&mut self, obj: &crate::Bound<'_, crate::PyAny>) { |
330 | self.failed = Some(obj.downcast::<PyIterator>().unwrap_err().into()); |
331 | } |
332 | } |
333 | |
334 | // Regression test for 2913 |
335 | Python::with_gil(|py| { |
336 | let downcaster = crate::Py::new(py, Downcaster { failed: None }).unwrap(); |
337 | crate::py_run!( |
338 | py, |
339 | downcaster, |
340 | r#" |
341 | from collections.abc import Sequence |
342 | |
343 | class MySequence(Sequence): |
344 | def __init__(self): |
345 | self._data = [1, 2, 3] |
346 | |
347 | def __getitem__(self, index): |
348 | return self._data[index] |
349 | |
350 | def __len__(self): |
351 | return len(self._data) |
352 | |
353 | downcaster.downcast_iterator(MySequence()) |
354 | "# |
355 | ); |
356 | |
357 | assert_eq!( |
358 | downcaster.borrow_mut(py).failed.take().unwrap().to_string(), |
359 | "TypeError: 'MySequence' object cannot be converted to 'Iterator'" |
360 | ); |
361 | }); |
362 | } |
363 | |
364 | #[test ] |
365 | #[cfg (feature = "macros" )] |
366 | fn python_class_iterator() { |
367 | #[crate::pyfunction (crate = "crate" )] |
368 | fn assert_iterator(obj: &crate::Bound<'_, crate::PyAny>) { |
369 | assert!(obj.downcast::<PyIterator>().is_ok()) |
370 | } |
371 | |
372 | // Regression test for 2913 |
373 | Python::with_gil(|py| { |
374 | let assert_iterator = crate::wrap_pyfunction!(assert_iterator, py).unwrap(); |
375 | crate::py_run!( |
376 | py, |
377 | assert_iterator, |
378 | r#" |
379 | class MyIter: |
380 | def __next__(self): |
381 | raise StopIteration |
382 | |
383 | assert_iterator(MyIter()) |
384 | "# |
385 | ); |
386 | }); |
387 | } |
388 | |
389 | #[test ] |
390 | #[cfg (not(Py_LIMITED_API))] |
391 | fn length_hint_becomes_size_hint_lower_bound() { |
392 | Python::with_gil(|py| { |
393 | let list = py.eval(ffi::c_str!("[1, 2, 3]" ), None, None).unwrap(); |
394 | let iter = list.try_iter().unwrap(); |
395 | let hint = iter.size_hint(); |
396 | assert_eq!(hint, (3, None)); |
397 | }); |
398 | } |
399 | } |
400 | |