Skip to content

Commit eed1b1a

Browse files
authored
Merge pull request #1560 from davidhewitt/deprecate-pyproto-pymethods
pyproto: deprecate py_methods
2 parents 88d86a6 + 48823e2 commit eed1b1a

File tree

14 files changed

+108
-29
lines changed

14 files changed

+108
-29
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2929
- Deprecate `PyModule` methods `call`, `call0`, `call1` and `get`. [#1492](https://github.com/PyO3/pyo3/pull/1492)
3030
- Add length information to `PyBufferError`s raised from `PyBuffer::copy_to_slice` and `PyBuffer::copy_from_slice`. [#1534](https://github.com/PyO3/pyo3/pull/1534)
3131
- Automatically provide `-undefined` and `dynamic_lookup` linker arguments on macOS with `extension-module` feature. [#1539](https://github.com/PyO3/pyo3/pull/1539)
32+
- Deprecate `#[pyproto]` methods which are easier to implement as `#[pymethods]`: [#1560](https://github.com/PyO3/pyo3/pull/1560)
33+
- `PyBasicProtocol::__bytes__` and `PyBasicProtocol::__format__`
34+
- `PyContextProtocol::__enter__` and `PyContextProtocol::__exit__`
35+
- `PyDescrProtocol::__delete__` and `PyDescrProtocol::__set_name__`
36+
- `PyMappingProtocol::__reversed__`
37+
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
38+
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
3239

3340
### Removed
3441
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)

benches/bench_pyclass.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ impl PyObjectProtocol for MyClass {
3131
fn __str__(&self) -> &'static str {
3232
"MyClass"
3333
}
34-
35-
fn __bytes__(&self) -> &'static [u8] {
36-
b"MyClass"
37-
}
3834
}
3935

4036
#[bench]

guide/src/class/protocols.md

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
## Class customizations
22

3-
Python's object model defines several protocols for different object behavior, like sequence,
4-
mapping or number protocols. PyO3 defines separate traits for each of them. To provide specific
5-
Python object behavior, you need to implement the specific trait for your struct. Important note,
6-
each protocol implementation block has to be annotated with the `#[pyproto]` attribute.
3+
PyO3 uses the `#[pyproto]` attribute in combination with special traits to implement certain protocol (aka `__dunder__`) methods of Python classes. The special traits are listed in this chapter of the guide. See also the [documentation for the `pyo3::class` module]({{#PYO3_DOCS_URL}}/pyo3/class/index.html).
74

8-
All `#[pyproto]` methods which can be defined below can return `T` instead of `PyResult<T>` if the
9-
method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether.
5+
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. You may be familiar with implementing these protocols in Python classes by "dunder" methods, such as `__str__` or `__repr__`.
6+
7+
In the Python C-API which PyO3 is dependent upon, many of these protocol methods have to be provided into special "slots" on the class type object. To fill these slots PyO3 uses the `#[pyproto]` attribute in combination with special traits.
8+
9+
All `#[pyproto]` methods can return `T` instead of `PyResult<T>` if the method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether.
10+
11+
There are many "dunder" methods which are not included in any of PyO3's protocol traits, such as `__dir__`. These methods can be implemented in `#[pymethods]` as already covered in the previous section.
1012

1113
### Basic object customization
1214

@@ -29,15 +31,6 @@ Each method corresponds to Python's `self.attr`, `self.attr = value` and `del se
2931

3032
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
3133

32-
* `fn __bytes__(&self) -> PyResult<PyBytes>`
33-
34-
Provides the conversion to `bytes`.
35-
36-
* `fn __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
37-
38-
Special method that is used by the `format()` builtin and the `str.format()` method.
39-
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
40-
4134
#### Comparison operators
4235

4336
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
@@ -132,14 +125,12 @@ The following methods implement the unary arithmetic operations (`-`, `+`, `abs(
132125

133126
Support for coercions:
134127

135-
* `fn __complex__(&'p self) -> PyResult<impl ToPyObject>`
136128
* `fn __int__(&'p self) -> PyResult<impl ToPyObject>`
137129
* `fn __float__(&'p self) -> PyResult<impl ToPyObject>`
138130

139131
Other:
140132

141133
* `fn __index__(&'p self) -> PyResult<impl ToPyObject>`
142-
* `fn __round__(&'p self, ndigits: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
143134

144135
### Emulating sequential containers (such as lists or tuples)
145136

@@ -237,12 +228,6 @@ For a mapping, the keys may be Python objects of arbitrary type.
237228
The same exceptions should be raised for improper key values as
238229
for the `__getitem__()` method.
239230

240-
* `fn __reversed__(&self) -> PyResult<impl ToPyObject>`
241-
242-
Called (if present) by the `reversed()` built-in to implement reverse iteration.
243-
It should return a new iterator object that iterates over all the objects in
244-
the container in reverse order.
245-
246231
### Garbage Collector Integration
247232

248233
If your type owns references to other Python objects, you will need to

guide/src/migration.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,47 @@ For projects embedding Python in Rust, PyO3 no longer automatically initalizes a
1515

1616
The limitation of the new default implementation is that it cannot support multiple `#[pymethods]` blocks for the same `#[pyclass]`. If you need this functionality, you must enable the `multiple-pymethods` feature which will switch `#[pymethods]` to the inventory-based implementation.
1717

18+
### Deprecated `#[pyproto]` methods
19+
20+
Some protocol (aka `__dunder__`) methods such as `__bytes__` and `__format__` have been possible to implement two ways in PyO3 for some time: via a `#[pyproto]` (e.g. `PyBasicProtocol` for the methods listed here), or by writing them directly in `#[pymethods]`. This is only true for a handful of the `#[pyproto]` methods (for technical reasons to do with the way PyO3 currently interacts with the Python C-API).
21+
22+
In the interest of having onle one way to do things, the `#[pyproto]` forms of these methods have been deprecated.
23+
24+
To migrate just move the affected methods from a `#[pyproto]` to a `#[pymethods]` block.
25+
26+
Before:
27+
28+
```rust,ignore
29+
use pyo3::prelude::*;
30+
use pyo3::class::basic::PyBasicProtocol;
31+
32+
#[pyclass]
33+
struct MyClass { }
34+
35+
#[pyproto]
36+
impl PyBasicProtocol for MyClass {
37+
fn __bytes__(&self) -> &'static [u8] {
38+
b"hello, world"
39+
}
40+
}
41+
```
42+
43+
After:
44+
45+
```rust
46+
use pyo3::prelude::*;
47+
48+
#[pyclass]
49+
struct MyClass { }
50+
51+
#[pymethods]
52+
impl MyClass {
53+
fn __bytes__(&self) -> &'static [u8] {
54+
b"hello, world"
55+
}
56+
}
57+
```
58+
1859
## from 0.12.* to 0.13
1960

2061
### Minimum Rust version increased to Rust 1.45

pyo3-macros-backend/src/defs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,15 +268,15 @@ pub const DESCR: Proto = Proto {
268268
methods: &[
269269
MethodProto::new("__get__", "PyDescrGetProtocol").args(&["Receiver", "Inst", "Owner"]),
270270
MethodProto::new("__set__", "PyDescrSetProtocol").args(&["Receiver", "Inst", "Value"]),
271-
MethodProto::new("__det__", "PyDescrDelProtocol")
271+
MethodProto::new("__delete__", "PyDescrDelProtocol")
272272
.args(&["Inst"])
273273
.has_self(),
274274
MethodProto::new("__set_name__", "PyDescrSetNameProtocol")
275275
.args(&["Inst"])
276276
.has_self(),
277277
],
278278
py_methods: &[
279-
PyMethod::new("__del__", "PyDescrDelProtocolImpl"),
279+
PyMethod::new("__delete__", "PyDescrDelProtocolImpl"),
280280
PyMethod::new("__set_name__", "PyDescrNameProtocolImpl"),
281281
],
282282
slot_defs: &[

src/class/basic.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ pub trait PyObjectProtocol<'p>: PyClass {
6161
unimplemented!()
6262
}
6363

64+
#[deprecated(
65+
since = "0.14.0",
66+
note = "prefer implementing `__format__` in `#[pymethods]` instead of in a protocol"
67+
)]
6468
fn __format__(&'p self, format_spec: Self::Format) -> Self::Result
6569
where
6670
Self: PyObjectFormatProtocol<'p>,
@@ -75,6 +79,10 @@ pub trait PyObjectProtocol<'p>: PyClass {
7579
unimplemented!()
7680
}
7781

82+
#[deprecated(
83+
since = "0.14.0",
84+
note = "prefer implementing `__bytes__` in `#[pymethods]` instead of in a protocol"
85+
)]
7886
fn __bytes__(&'p self) -> Self::Result
7987
where
8088
Self: PyObjectBytesProtocol<'p>,

src/class/context.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,21 @@ use crate::{PyClass, PyObject};
1010
/// Context manager interface
1111
#[allow(unused_variables)]
1212
pub trait PyContextProtocol<'p>: PyClass {
13+
#[deprecated(
14+
since = "0.14.0",
15+
note = "prefer implementing `__enter__` in `#[pymethods]` instead of in a protocol"
16+
)]
1317
fn __enter__(&'p mut self) -> Self::Result
1418
where
1519
Self: PyContextEnterProtocol<'p>,
1620
{
1721
unimplemented!()
1822
}
1923

24+
#[deprecated(
25+
since = "0.14.0",
26+
note = "prefer implementing `__exit__` in `#[pymethods]` instead of in a protocol"
27+
)]
2028
fn __exit__(
2129
&'p mut self,
2230
exc_type: Option<Self::ExcType>,

src/class/descr.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,21 @@ pub trait PyDescrProtocol<'p>: PyClass {
3131
unimplemented!()
3232
}
3333

34+
#[deprecated(
35+
since = "0.14.0",
36+
note = "prefer implementing `__delete__` in `#[pymethods]` instead of in a protocol"
37+
)]
3438
fn __delete__(&'p self, instance: &'p PyAny) -> Self::Result
3539
where
3640
Self: PyDescrDeleteProtocol<'p>,
3741
{
3842
unimplemented!()
3943
}
4044

45+
#[deprecated(
46+
since = "0.14.0",
47+
note = "prefer implementing `__set_name__` in `#[pymethods]` instead of in a protocol"
48+
)]
4149
fn __set_name__(&'p self, instance: &'p PyAny) -> Self::Result
4250
where
4351
Self: PyDescrSetNameProtocol<'p>,

src/class/mapping.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ pub trait PyMappingProtocol<'p>: PyClass {
3737
unimplemented!()
3838
}
3939

40+
#[deprecated(
41+
since = "0.14.0",
42+
note = "prefer implementing `__reversed__` in `#[pymethods]` instead of in a protocol"
43+
)]
4044
fn __reversed__(&'p self) -> Self::Result
4145
where
4246
Self: PyMappingReversedProtocol<'p>,

src/class/number.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ pub trait PyNumberProtocol<'p>: PyClass {
283283
{
284284
unimplemented!()
285285
}
286+
#[deprecated(
287+
since = "0.14.0",
288+
note = "prefer implementing `__complex__` in `#[pymethods]` instead of in a protocol"
289+
)]
286290
fn __complex__(&'p self) -> Self::Result
287291
where
288292
Self: PyNumberComplexProtocol<'p>,
@@ -307,6 +311,10 @@ pub trait PyNumberProtocol<'p>: PyClass {
307311
{
308312
unimplemented!()
309313
}
314+
#[deprecated(
315+
since = "0.14.0",
316+
note = "prefer implementing `__round__` in `#[pymethods]` instead of in a protocol"
317+
)]
310318
fn __round__(&'p self, ndigits: Option<Self::NDigits>) -> Self::Result
311319
where
312320
Self: PyNumberRoundProtocol<'p>,

0 commit comments

Comments
 (0)