Skip to content

Commit 3856c51

Browse files
committed
Change return types of py.None(), py.NotImplemented() and py.Ellipsis() to typed singletons
1 parent d89c388 commit 3856c51

File tree

20 files changed

+81
-40
lines changed

20 files changed

+81
-40
lines changed

guide/src/class/protocols.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ given signatures should be interpreted as follows:
103103
match op {
104104
CompareOp::Eq => (self.0 == other.0).into_py(py),
105105
CompareOp::Ne => (self.0 != other.0).into_py(py),
106-
_ => py.NotImplemented(),
106+
_ => py.NotImplemented().into(),
107107
}
108108
}
109109
}

guide/src/migration.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,42 @@
33
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
44
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
55

6+
## from 0.20.* to 0.21
7+
8+
### `py.None()`, `py.NotImplemented()` and `py.Ellipsis()` now return typed singletons
9+
10+
Previously `py.None()`, `py.NotImplemented()` and `py.Ellipsis()` would return `PyObject`. This had a few downsides:
11+
- `PyObject` does not carry static type information
12+
- `PyObject` takes ownership of a reference to the singletons, adding refcounting performance overhead
13+
- `PyObject` is not gil-bound, meaning follow up method calls might again need `py`, causing repetition
14+
15+
To avoid these downsides, these methods now return typed gil-bound references to the singletons, e.g. `py.None()` returns `&PyNone`. These typed singletons all implement `Into<PyObject>`, so migration is straightforward.
16+
17+
Before:
18+
19+
```rust,compile_fail
20+
# use pyo3::prelude::*;
21+
Python::with_gil(|py| {
22+
let a: PyObject = py.None();
23+
24+
let b: &PyAny = py.None().as_ref(py); // or into_ref(py)
25+
});
26+
```
27+
28+
After:
29+
30+
```rust
31+
# use pyo3::prelude::*;
32+
Python::with_gil(|py| {
33+
// For uses needing a PyObject, add `.into()`
34+
let a: PyObject = py.None().into();
35+
36+
// For uses needing &PyAny, remove `.as_ref(py)`
37+
let b: &PyAny = py.None();
38+
});
39+
```
40+
41+
642
## from 0.19.* to 0.20
743

844
### Drop support for older technologies
@@ -158,7 +194,7 @@ fn raise_err() -> anyhow::Result<()> {
158194
Err(PyValueError::new_err("original error message").into())
159195
}
160196

161-
fn main() {
197+
# fn main() {
162198
Python::with_gil(|py| {
163199
let rs_func = wrap_pyfunction!(raise_err, py).unwrap();
164200
pyo3::py_run!(
@@ -936,14 +972,14 @@ ensure that the Python GIL was held by the current thread). Technically, this wa
936972
To migrate, just pass a `py` argument to any calls to these methods.
937973

938974
Before:
939-
```rust,compile_fail
975+
```rust,ignore
940976
# pyo3::Python::with_gil(|py| {
941977
py.None().get_refcnt();
942978
# })
943979
```
944980

945981
After:
946-
```rust
982+
```rust,compile_fail
947983
# pyo3::Python::with_gil(|py| {
948984
py.None().get_refcnt(py);
949985
# })

newsfragments/3578.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Change return type of `py.None()`, `py.NotImplemented()`, and `py.Ellipsis()` from `PyObject` to typed singletons (`&PyNone`, `&PyNotImplemented` and `PyEllipsis` respectively).

pyo3-benches/benches/bench_gil.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn bench_clean_acquire_gil(b: &mut Bencher<'_>) {
1616
}
1717

1818
fn bench_dirty_acquire_gil(b: &mut Bencher<'_>) {
19-
let obj = Python::with_gil(|py| py.None());
19+
let obj: PyObject = Python::with_gil(|py| py.None().into());
2020
b.iter_batched(
2121
|| {
2222
// Clone and drop an object so that the GILPool has work to do.

pyo3-benches/benches/bench_pyobject.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn drop_many_objects(b: &mut Bencher<'_>) {
66
Python::with_gil(|py| {
77
b.iter(|| {
88
for _ in 0..1000 {
9-
std::mem::drop(py.None());
9+
std::mem::drop(PyObject::from(py.None()));
1010
}
1111
});
1212
});

pyo3-macros-backend/src/pyclass.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ fn impl_enum(
587587
return Ok((self_val == other.__pyo3__int__()).to_object(py));
588588
}
589589

590-
return Ok(py.NotImplemented());
590+
return Ok(::std::convert::Into::into(py.NotImplemented()));
591591
}
592592
_pyo3::basic::CompareOp::Ne => {
593593
let self_val = self.__pyo3__int__();
@@ -598,9 +598,9 @@ fn impl_enum(
598598
return Ok((self_val != other.__pyo3__int__()).to_object(py));
599599
}
600600

601-
return Ok(py.NotImplemented());
601+
return Ok(::std::convert::Into::into(py.NotImplemented()));
602602
}
603-
_ => Ok(py.NotImplemented()),
603+
_ => Ok(::std::convert::Into::into(py.NotImplemented())),
604604
}
605605
}
606606
};

pytests/src/awaitable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl IterAwaitable {
3636
Ok(v) => Ok(IterNextOutput::Return(v)),
3737
Err(err) => Err(err),
3838
},
39-
_ => Ok(IterNextOutput::Yield(py.None())),
39+
_ => Ok(IterNextOutput::Yield(py.None().into())),
4040
}
4141
}
4242
}

src/conversion.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub trait ToPyObject {
141141
/// match self {
142142
/// Self::Integer(val) => val.into_py(py),
143143
/// Self::String(val) => val.into_py(py),
144-
/// Self::None => py.None(),
144+
/// Self::None => py.None().into(),
145145
/// }
146146
/// }
147147
/// }
@@ -251,7 +251,7 @@ where
251251
{
252252
fn to_object(&self, py: Python<'_>) -> PyObject {
253253
self.as_ref()
254-
.map_or_else(|| py.None(), |val| val.to_object(py))
254+
.map_or_else(|| py.None().into(), |val| val.to_object(py))
255255
}
256256
}
257257

@@ -260,7 +260,7 @@ where
260260
T: IntoPy<PyObject>,
261261
{
262262
fn into_py(self, py: Python<'_>) -> PyObject {
263-
self.map_or_else(|| py.None(), |val| val.into_py(py))
263+
self.map_or_else(|| py.None().into(), |val| val.into_py(py))
264264
}
265265
}
266266

@@ -622,13 +622,13 @@ mod tests {
622622
assert_eq!(option.as_ptr(), std::ptr::null_mut());
623623

624624
let none = py.None();
625-
option = Some(none.clone());
625+
option = Some(none.into());
626626

627-
let ref_cnt = none.get_refcnt(py);
627+
let ref_cnt = none.get_refcnt();
628628
assert_eq!(option.as_ptr(), none.as_ptr());
629629

630630
// Ensure ref count not changed by as_ptr call
631-
assert_eq!(none.get_refcnt(py), ref_cnt);
631+
assert_eq!(none.get_refcnt(), ref_cnt);
632632
});
633633
}
634634
}

src/err/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl PyErr {
181181
} else {
182182
// Assume obj is Type[Exception]; let later normalization handle if this
183183
// is not the case
184-
PyErrState::lazy(obj, obj.py().None())
184+
PyErrState::lazy(obj, Option::<PyObject>::None)
185185
};
186186

187187
PyErr::from_state(state)

src/ffi/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,15 +314,15 @@ fn test_inc_dec_ref_immortal() {
314314
Python::with_gil(|py| {
315315
let obj = py.None();
316316

317-
let ref_count = obj.get_refcnt(py);
317+
let ref_count = obj.get_refcnt();
318318
let ptr = obj.as_ptr();
319319

320320
unsafe { Py_INCREF(ptr) };
321321

322-
assert_eq!(obj.get_refcnt(py), ref_count);
322+
assert_eq!(obj.get_refcnt(), ref_count);
323323

324324
unsafe { Py_DECREF(ptr) };
325325

326-
assert_eq!(obj.get_refcnt(py), ref_count);
326+
assert_eq!(obj.get_refcnt(), ref_count);
327327
})
328328
}

0 commit comments

Comments
 (0)