Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pyclass eq option #4210

Merged
merged 6 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions guide/pyclass-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
| `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. |
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
| `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. |
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
Expand Down
23 changes: 14 additions & 9 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ struct Number(i32);

// PyO3 supports unit-only enums (which contain only unit variants)
// These simple enums behave similarly to Python's enumerations (enum.Enum)
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 30, // PyO3 supports custom discriminants.
}

// PyO3 supports custom discriminants in unit-only enums
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum HttpResponse {
Ok = 200,
NotFound = 404,
Expand Down Expand Up @@ -1053,7 +1055,8 @@ PyO3 adds a class attribute for each variant, so you can access them in Python w

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant,
Expand All @@ -1075,7 +1078,8 @@ You can also convert your simple enums into `int`:

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 10,
Expand All @@ -1087,8 +1091,6 @@ Python::with_gil(|py| {
pyo3::py_run!(py, cls x, r#"
assert int(cls.Variant) == x
assert int(cls.OtherVariant) == 10
assert cls.OtherVariant == 10 # You can also compare against int.
assert 10 == cls.OtherVariant
"#)
})
```
Expand All @@ -1097,7 +1099,8 @@ PyO3 also provides `__repr__` for enums:

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum{
Variant,
OtherVariant,
Expand All @@ -1117,7 +1120,8 @@ All methods defined by PyO3 can be overridden. For example here's how you overri

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Answer = 42,
}
Expand All @@ -1139,7 +1143,8 @@ Enums and their variants can also be renamed using `#[pyo3(name)]`.

```rust
# use pyo3::prelude::*;
#[pyclass(name = "RenamedEnum")]
#[pyclass(eq, eq_int, name = "RenamedEnum")]
#[derive(PartialEq)]
enum MyEnum {
#[pyo3(name = "UPPERCASE")]
Variant,
Expand Down
11 changes: 11 additions & 0 deletions guide/src/class/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ impl Number {
# }
```

To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used.

```rust
# use pyo3::prelude::*;
#
#[pyclass(eq)]
#[derive(PartialEq)]
struct Number(i32);
```

### Truthyness

We'll consider `Number` to be `True` if it is nonzero:
Expand Down Expand Up @@ -305,3 +315,4 @@ fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
[SipHash]: https://en.wikipedia.org/wiki/SipHash
[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html
34 changes: 34 additions & 0 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ However, take care to note that the behaviour is different from previous version
Related to this, we also added a `pyo3_disable_reference_pool` conditional compilation flag which removes the infrastructure necessary to apply delayed reference count decrements implied by `impl<T> Drop for Py<T>`. They do not appear to be a soundness hazard as they should lead to memory leaks in the worst case. However, the global synchronization adds significant overhead to cross the Python-Rust boundary. Enabling this feature will remove these costs and make the `Drop` implementation abort the process if called without the GIL being held instead.
</details>

### Require explicit opt-in for comparison for simple enums
<details open>
<summary><small>Click to expand</small></summary>

With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute.

To migrate, place a `#[pyo3(eq, eq_int)]` attribute on simple enum classes.

Before:

```rust
# #![allow(deprecated, dead_code)]
# use pyo3::prelude::*;
#[pyclass]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```

After:

```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```
</details>

## from 0.20.* to 0.21
<details open>
<summary><small>Click to expand</small></summary>
Expand Down
2 changes: 2 additions & 0 deletions newsfragments/4210.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`.
Added `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants.
1 change: 1 addition & 0 deletions newsfragments/4210.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`.
2 changes: 2 additions & 0 deletions pyo3-macros-backend/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod kw {
syn::custom_keyword!(cancel_handle);
syn::custom_keyword!(constructor);
syn::custom_keyword!(dict);
syn::custom_keyword!(eq);
syn::custom_keyword!(eq_int);
syn::custom_keyword!(extends);
syn::custom_keyword!(freelist);
syn::custom_keyword!(from_py_with);
Expand Down
Loading
Loading