Skip to content

Commit 908ef6a

Browse files
codeguru42Icxolu
andauthored
Implement PartialEq for PyBytes and [u8] (#4259)
* Copy pasta implementation from types/string.rs * changelog * I think I don't need a special implementation for 3.10 or ABI * Copy pasta tests * Fix comment with correct type Co-authored-by: Icxolu <[email protected]> * Fix implementation * Use slice in tests * Try renaming changelog file * Fix doc example * Fix doc example Co-authored-by: Icxolu <[email protected]> --------- Co-authored-by: Icxolu <[email protected]>
1 parent c4d18e5 commit 908ef6a

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

newsfragments/4250.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement `PartialEq<str>` for `Bound<'py, PyBytes>`.
File renamed without changes.

src/types/bytes.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,35 @@ use std::str;
1111
/// Represents a Python `bytes` object.
1212
///
1313
/// This type is immutable.
14+
///
15+
/// # Equality
16+
///
17+
/// For convenience, [`Bound<'py, PyBytes>`] implements [`PartialEq<[u8]>`] to allow comparing the
18+
/// data in the Python bytes to a Rust `[u8]`.
19+
///
20+
/// This is not always the most appropriate way to compare Python bytes, as Python bytes subclasses
21+
/// may have different equality semantics. In situations where subclasses overriding equality might be
22+
/// relevant, use [`PyAnyMethods::eq`], at cost of the additional overhead of a Python method call.
23+
///
24+
/// ```rust
25+
/// # use pyo3::prelude::*;
26+
/// use pyo3::types::PyBytes;
27+
///
28+
/// # Python::with_gil(|py| {
29+
/// let py_bytes = PyBytes::new_bound(py, b"foo".as_slice());
30+
/// // via PartialEq<[u8]>
31+
/// assert_eq!(py_bytes, b"foo".as_slice());
32+
///
33+
/// // via Python equality
34+
/// let other = PyBytes::new_bound(py, b"foo".as_slice());
35+
/// assert!(py_bytes.as_any().eq(other).unwrap());
36+
///
37+
/// // Note that `eq` will convert it's argument to Python using `ToPyObject`,
38+
/// // so the following does not compare equal since the slice will convert into a
39+
/// // `list`, not a `bytes` object.
40+
/// assert!(!py_bytes.as_any().eq(b"foo".as_slice()).unwrap());
41+
/// # });
42+
/// ```
1443
#[repr(transparent)]
1544
pub struct PyBytes(PyAny);
1645

@@ -191,6 +220,106 @@ impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> {
191220
}
192221
}
193222

223+
/// Compares whether the Python bytes object is equal to the [u8].
224+
///
225+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
226+
impl PartialEq<[u8]> for Bound<'_, PyBytes> {
227+
#[inline]
228+
fn eq(&self, other: &[u8]) -> bool {
229+
self.as_borrowed() == *other
230+
}
231+
}
232+
233+
/// Compares whether the Python bytes object is equal to the [u8].
234+
///
235+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
236+
impl PartialEq<&'_ [u8]> for Bound<'_, PyBytes> {
237+
#[inline]
238+
fn eq(&self, other: &&[u8]) -> bool {
239+
self.as_borrowed() == **other
240+
}
241+
}
242+
243+
/// Compares whether the Python bytes object is equal to the [u8].
244+
///
245+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
246+
impl PartialEq<Bound<'_, PyBytes>> for [u8] {
247+
#[inline]
248+
fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
249+
*self == other.as_borrowed()
250+
}
251+
}
252+
253+
/// Compares whether the Python bytes object is equal to the [u8].
254+
///
255+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
256+
impl PartialEq<&'_ Bound<'_, PyBytes>> for [u8] {
257+
#[inline]
258+
fn eq(&self, other: &&Bound<'_, PyBytes>) -> bool {
259+
*self == other.as_borrowed()
260+
}
261+
}
262+
263+
/// Compares whether the Python bytes object is equal to the [u8].
264+
///
265+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
266+
impl PartialEq<Bound<'_, PyBytes>> for &'_ [u8] {
267+
#[inline]
268+
fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
269+
**self == other.as_borrowed()
270+
}
271+
}
272+
273+
/// Compares whether the Python bytes object is equal to the [u8].
274+
///
275+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
276+
impl PartialEq<[u8]> for &'_ Bound<'_, PyBytes> {
277+
#[inline]
278+
fn eq(&self, other: &[u8]) -> bool {
279+
self.as_borrowed() == other
280+
}
281+
}
282+
283+
/// Compares whether the Python bytes object is equal to the [u8].
284+
///
285+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
286+
impl PartialEq<[u8]> for Borrowed<'_, '_, PyBytes> {
287+
#[inline]
288+
fn eq(&self, other: &[u8]) -> bool {
289+
self.as_bytes() == other
290+
}
291+
}
292+
293+
/// Compares whether the Python bytes object is equal to the [u8].
294+
///
295+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
296+
impl PartialEq<&[u8]> for Borrowed<'_, '_, PyBytes> {
297+
#[inline]
298+
fn eq(&self, other: &&[u8]) -> bool {
299+
*self == **other
300+
}
301+
}
302+
303+
/// Compares whether the Python bytes object is equal to the [u8].
304+
///
305+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
306+
impl PartialEq<Borrowed<'_, '_, PyBytes>> for [u8] {
307+
#[inline]
308+
fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
309+
other == self
310+
}
311+
}
312+
313+
/// Compares whether the Python bytes object is equal to the [u8].
314+
///
315+
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
316+
impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
317+
#[inline]
318+
fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
319+
other == self
320+
}
321+
}
322+
194323
#[cfg(test)]
195324
mod tests {
196325
use super::*;
@@ -251,4 +380,34 @@ mod tests {
251380
.is_instance_of::<PyValueError>(py));
252381
});
253382
}
383+
384+
#[test]
385+
fn test_comparisons() {
386+
Python::with_gil(|py| {
387+
let b = b"hello, world".as_slice();
388+
let py_bytes = PyBytes::new_bound(py, b);
389+
390+
assert_eq!(py_bytes, b"hello, world".as_slice());
391+
392+
assert_eq!(py_bytes, b);
393+
assert_eq!(&py_bytes, b);
394+
assert_eq!(b, py_bytes);
395+
assert_eq!(b, &py_bytes);
396+
397+
assert_eq!(py_bytes, *b);
398+
assert_eq!(&py_bytes, *b);
399+
assert_eq!(*b, py_bytes);
400+
assert_eq!(*b, &py_bytes);
401+
402+
let py_string = py_bytes.as_borrowed();
403+
404+
assert_eq!(py_string, b);
405+
assert_eq!(&py_string, b);
406+
assert_eq!(b, py_string);
407+
assert_eq!(b, &py_string);
408+
409+
assert_eq!(py_string, *b);
410+
assert_eq!(*b, py_string);
411+
})
412+
}
254413
}

0 commit comments

Comments
 (0)