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 <10486322+Icxolu@users.noreply.github.com>

* Fix implementation

* Use slice in tests

* Try renaming changelog file

* Fix doc example

* Fix doc example

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>

---------

Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
This commit is contained in:
Code Apprentice 2024-06-22 16:08:57 -06:00 committed by GitHub
parent c4d18e5ee3
commit 908ef6ad84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 160 additions and 0 deletions

View File

@ -0,0 +1 @@
Implement `PartialEq<str>` for `Bound<'py, PyBytes>`.

View File

@ -11,6 +11,35 @@ use std::str;
/// Represents a Python `bytes` object. /// Represents a Python `bytes` object.
/// ///
/// This type is immutable. /// This type is immutable.
///
/// # Equality
///
/// For convenience, [`Bound<'py, PyBytes>`] implements [`PartialEq<[u8]>`] to allow comparing the
/// data in the Python bytes to a Rust `[u8]`.
///
/// This is not always the most appropriate way to compare Python bytes, as Python bytes subclasses
/// may have different equality semantics. In situations where subclasses overriding equality might be
/// relevant, use [`PyAnyMethods::eq`], at cost of the additional overhead of a Python method call.
///
/// ```rust
/// # use pyo3::prelude::*;
/// use pyo3::types::PyBytes;
///
/// # Python::with_gil(|py| {
/// let py_bytes = PyBytes::new_bound(py, b"foo".as_slice());
/// // via PartialEq<[u8]>
/// assert_eq!(py_bytes, b"foo".as_slice());
///
/// // via Python equality
/// let other = PyBytes::new_bound(py, b"foo".as_slice());
/// assert!(py_bytes.as_any().eq(other).unwrap());
///
/// // Note that `eq` will convert it's argument to Python using `ToPyObject`,
/// // so the following does not compare equal since the slice will convert into a
/// // `list`, not a `bytes` object.
/// assert!(!py_bytes.as_any().eq(b"foo".as_slice()).unwrap());
/// # });
/// ```
#[repr(transparent)] #[repr(transparent)]
pub struct PyBytes(PyAny); pub struct PyBytes(PyAny);
@ -191,6 +220,106 @@ impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> {
} }
} }
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<[u8]> for Bound<'_, PyBytes> {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.as_borrowed() == *other
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<&'_ [u8]> for Bound<'_, PyBytes> {
#[inline]
fn eq(&self, other: &&[u8]) -> bool {
self.as_borrowed() == **other
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<Bound<'_, PyBytes>> for [u8] {
#[inline]
fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
*self == other.as_borrowed()
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<&'_ Bound<'_, PyBytes>> for [u8] {
#[inline]
fn eq(&self, other: &&Bound<'_, PyBytes>) -> bool {
*self == other.as_borrowed()
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<Bound<'_, PyBytes>> for &'_ [u8] {
#[inline]
fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
**self == other.as_borrowed()
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<[u8]> for &'_ Bound<'_, PyBytes> {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.as_borrowed() == other
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<[u8]> for Borrowed<'_, '_, PyBytes> {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.as_bytes() == other
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<&[u8]> for Borrowed<'_, '_, PyBytes> {
#[inline]
fn eq(&self, other: &&[u8]) -> bool {
*self == **other
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<Borrowed<'_, '_, PyBytes>> for [u8] {
#[inline]
fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
other == self
}
}
/// Compares whether the Python bytes object is equal to the [u8].
///
/// In some cases Python equality might be more appropriate; see the note on [`PyBytes`].
impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
#[inline]
fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
other == self
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -251,4 +380,34 @@ mod tests {
.is_instance_of::<PyValueError>(py)); .is_instance_of::<PyValueError>(py));
}); });
} }
#[test]
fn test_comparisons() {
Python::with_gil(|py| {
let b = b"hello, world".as_slice();
let py_bytes = PyBytes::new_bound(py, b);
assert_eq!(py_bytes, b"hello, world".as_slice());
assert_eq!(py_bytes, b);
assert_eq!(&py_bytes, b);
assert_eq!(b, py_bytes);
assert_eq!(b, &py_bytes);
assert_eq!(py_bytes, *b);
assert_eq!(&py_bytes, *b);
assert_eq!(*b, py_bytes);
assert_eq!(*b, &py_bytes);
let py_string = py_bytes.as_borrowed();
assert_eq!(py_string, b);
assert_eq!(&py_string, b);
assert_eq!(b, py_string);
assert_eq!(b, &py_string);
assert_eq!(py_string, *b);
assert_eq!(*b, py_string);
})
}
} }