Skip to content

Commit a605308

Browse files
committed
Add change log and migration guide entries.
1 parent 83697f0 commit a605308

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

guide/src/migration.md

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,124 @@ Python::with_gil(|py| {
7474
});
7575
```
7676

77-
### `PyType::name` is now `PyType::qualname`
77+
### `Iter(A)NextOutput` are deprecated
78+
79+
The `__next__` and `__anext__` magic methods can now return any type convertible into Python objects directly just like all other `#[pymethods]`. The `IterNextOutput` used by `__next__` and `IterANextOutput` used by `__anext__` are subsequently deprecated. Most importantly, this change allows returning an awaitable from `__anext__` without non-sensically wrapping it into `Yield` or `Some`. Only the return types `Option<T>` and `PyResult<Option<T>>` are still handled in a special manner where `Some(val)` yields `val` and `None` stops iteration.
80+
81+
Starting with an implementation of a Python iterator using `IterNextOutput`, e.g.
82+
83+
```rust
84+
#![allow(deprecated)]
85+
use pyo3::prelude::*;
86+
use pyo3::iter::IterNextOutput;
87+
88+
#[pyclass]
89+
struct PyClassIter {
90+
count: usize,
91+
}
92+
93+
#[pymethods]
94+
impl PyClassIter {
95+
fn __next__(&mut self) -> IterNextOutput<usize, &'static str> {
96+
if self.count < 5 {
97+
self.count += 1;
98+
IterNextOutput::Yield(self.count)
99+
} else {
100+
IterNextOutput::Return("done")
101+
}
102+
}
103+
}
104+
```
105+
106+
If returning `"done"` via `StopIteration` is not really required, this should be written as
107+
108+
```rust
109+
use pyo3::prelude::*;
110+
111+
#[pyclass]
112+
struct PyClassIter {
113+
count: usize,
114+
}
115+
116+
#[pymethods]
117+
impl PyClassIter {
118+
fn __next__(&mut self) -> Option<usize> {
119+
if self.count < 5 {
120+
self.count += 1;
121+
Some(self.count)
122+
} else {
123+
None
124+
}
125+
}
126+
}
127+
```
128+
129+
This form also has additional benefits: It has already worked in previous PyO3 versions, it matches the signature of Rust's [`Iterator` trait](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) and it allows using a fast path in CPython which completely avoids the cost of raising a `StopIteration` exception. Note that using [`Option::transpose`](https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose) and the `PyResult<Option<T>>` variant, this form can also be used to wrap fallible iterators.
130+
131+
Alternatively, the implementation can also be done as it would in Python itself, i.e. by "raising" a `StopIteration` exception
132+
133+
```rust
134+
use pyo3::prelude::*;
135+
use pyo3::exceptions::PyStopIteration;
136+
137+
#[pyclass]
138+
struct PyClassIter {
139+
count: usize,
140+
}
141+
142+
#[pymethods]
143+
impl PyClassIter {
144+
fn __next__(&mut self) -> PyResult<usize> {
145+
if self.count < 5 {
146+
self.count += 1;
147+
Ok(self.count)
148+
} else {
149+
Err(PyStopIteration::new_err("done"))
150+
}
151+
}
152+
}
153+
```
154+
155+
Finally, an asynchronous iterator can directly return an awaitable without confusing wrapping
156+
157+
```rust
158+
use pyo3::prelude::*;
159+
160+
#[pyclass]
161+
struct PyClassAwaitable {
162+
number: usize,
163+
}
164+
165+
#[pymethods]
166+
impl PyClassAwaitable {
167+
fn __next__(&self) -> usize {
168+
self.number
169+
}
170+
171+
fn __await__(slf: Py<Self>) -> Py<Self> {
172+
slf
173+
}
174+
}
175+
176+
#[pyclass]
177+
struct PyClassAsyncIter {
178+
number: usize,
179+
}
180+
181+
#[pymethods]
182+
impl PyClassAsyncIter {
183+
fn __anext__(&mut self) -> PyClassAwaitable {
184+
self.number += 1;
185+
PyClassAwaitable { number: self.number }
186+
}
187+
188+
fn __aiter__(slf: Py<Self>) -> Py<Self> {
189+
slf
190+
}
191+
}
192+
```
193+
194+
### `PyType::name` has been renamed to `PyType::qualname`
78195

79196
`PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes.
80197

newsfragments/3661.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The `Iter(A)NextOutput` types are now deprecated and `__(a)next__` can directly return anything which can be converted into Python objects, i.e. awaitables do not need to be wrapped into `IterANextOutput` or `Option` any more. `Option` can still be used as well and returning `None` will trigger the fast path for `__next__`, stopping iteration without having to raise a `StopIteration` exception.

0 commit comments

Comments
 (0)