implement `PartialEq<str>` for `Bound<'py, PyString>` (#4245)
* implement `PartialEq<str>` for `Bound<'py, PyString>` * fixup conditional code * document equality semantics for `Bound<'_, PyString>` * fix doc example
This commit is contained in:
parent
0b2f19b3c9
commit
9648d595a5
|
@ -0,0 +1 @@
|
||||||
|
Implement `PartialEq<str>` for `Bound<'py, PyString>`.
|
|
@ -328,6 +328,15 @@ extern "C" {
|
||||||
pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int;
|
pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int;
|
||||||
#[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")]
|
#[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")]
|
||||||
pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int;
|
pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int;
|
||||||
|
#[cfg(Py_3_13)]
|
||||||
|
pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int;
|
||||||
|
#[cfg(Py_3_13)]
|
||||||
|
pub fn PyUnicode_EqualToUTF8AndSize(
|
||||||
|
unicode: *mut PyObject,
|
||||||
|
string: *const c_char,
|
||||||
|
size: Py_ssize_t,
|
||||||
|
) -> c_int;
|
||||||
|
|
||||||
pub fn PyUnicode_RichCompare(
|
pub fn PyUnicode_RichCompare(
|
||||||
left: *mut PyObject,
|
left: *mut PyObject,
|
||||||
right: *mut PyObject,
|
right: *mut PyObject,
|
||||||
|
|
|
@ -2010,9 +2010,7 @@ impl PyObject {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Bound, Py, PyObject};
|
use super::{Bound, Py, PyObject};
|
||||||
use crate::types::any::PyAnyMethods;
|
use crate::types::{dict::IntoPyDict, PyAnyMethods, PyCapsule, PyDict, PyString};
|
||||||
use crate::types::{dict::IntoPyDict, PyDict, PyString};
|
|
||||||
use crate::types::{PyCapsule, PyStringMethods};
|
|
||||||
use crate::{ffi, Borrowed, PyAny, PyResult, Python, ToPyObject};
|
use crate::{ffi, Borrowed, PyAny, PyResult, Python, ToPyObject};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2021,7 +2019,7 @@ mod tests {
|
||||||
let obj = py.get_type_bound::<PyDict>().to_object(py);
|
let obj = py.get_type_bound::<PyDict>().to_object(py);
|
||||||
|
|
||||||
let assert_repr = |obj: &Bound<'_, PyAny>, expected: &str| {
|
let assert_repr = |obj: &Bound<'_, PyAny>, expected: &str| {
|
||||||
assert_eq!(obj.repr().unwrap().to_cow().unwrap(), expected);
|
assert_eq!(obj.repr().unwrap(), expected);
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_repr(obj.call0(py).unwrap().bind(py), "{}");
|
assert_repr(obj.call0(py).unwrap().bind(py), "{}");
|
||||||
|
@ -2221,7 +2219,7 @@ a = A()
|
||||||
let obj_unbound: Py<PyString> = obj.unbind();
|
let obj_unbound: Py<PyString> = obj.unbind();
|
||||||
let obj: Bound<'_, PyString> = obj_unbound.into_bound(py);
|
let obj: Bound<'_, PyString> = obj_unbound.into_bound(py);
|
||||||
|
|
||||||
assert_eq!(obj.to_cow().unwrap(), "hello world");
|
assert_eq!(obj, "hello world");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -515,12 +515,8 @@ impl<'py> TryFrom<&Bound<'py, PyAny>> for Bound<'py, PyByteArray> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::types::any::PyAnyMethods;
|
use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods};
|
||||||
use crate::types::bytearray::PyByteArrayMethods;
|
use crate::{exceptions, Bound, PyAny, PyObject, Python};
|
||||||
use crate::types::string::PyStringMethods;
|
|
||||||
use crate::types::PyByteArray;
|
|
||||||
use crate::{exceptions, Bound, PyAny};
|
|
||||||
use crate::{PyObject, Python};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_len() {
|
fn test_len() {
|
||||||
|
@ -555,10 +551,7 @@ mod tests {
|
||||||
|
|
||||||
slice[0..5].copy_from_slice(b"Hi...");
|
slice[0..5].copy_from_slice(b"Hi...");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(bytearray.str().unwrap(), "bytearray(b'Hi... Python')");
|
||||||
bytearray.str().unwrap().to_cow().unwrap(),
|
|
||||||
"bytearray(b'Hi... Python')"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl PyModule {
|
||||||
/// Python::with_gil(|py| -> PyResult<()> {
|
/// Python::with_gil(|py| -> PyResult<()> {
|
||||||
/// let module = PyModule::new_bound(py, "my_module")?;
|
/// let module = PyModule::new_bound(py, "my_module")?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(module.name()?.to_cow()?, "my_module");
|
/// assert_eq!(module.name()?, "my_module");
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// })?;
|
/// })?;
|
||||||
/// # Ok(())}
|
/// # Ok(())}
|
||||||
|
@ -728,7 +728,7 @@ fn __name__(py: Python<'_>) -> &Bound<'_, PyString> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{module::PyModuleMethods, string::PyStringMethods, PyModule},
|
types::{module::PyModuleMethods, PyModule},
|
||||||
Python,
|
Python,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -736,15 +736,13 @@ mod tests {
|
||||||
fn module_import_and_name() {
|
fn module_import_and_name() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let builtins = PyModule::import_bound(py, "builtins").unwrap();
|
let builtins = PyModule::import_bound(py, "builtins").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(builtins.name().unwrap(), "builtins");
|
||||||
builtins.name().unwrap().to_cow().unwrap().as_ref(),
|
|
||||||
"builtins"
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn module_filename() {
|
fn module_filename() {
|
||||||
|
use crate::types::string::PyStringMethods;
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let site = PyModule::import_bound(py, "site").unwrap();
|
let site = PyModule::import_bound(py, "site").unwrap();
|
||||||
assert!(site
|
assert!(site
|
||||||
|
|
|
@ -123,7 +123,33 @@ impl<'a> PyStringData<'a> {
|
||||||
|
|
||||||
/// Represents a Python `string` (a Unicode string object).
|
/// Represents a Python `string` (a Unicode string object).
|
||||||
///
|
///
|
||||||
/// This type is immutable.
|
/// This type is only seen inside PyO3's smart pointers as [`Py<PyString>`], [`Bound<'py, PyString>`],
|
||||||
|
/// and [`Borrowed<'a, 'py, PyString>`].
|
||||||
|
///
|
||||||
|
/// All functionality on this type is implemented through the [`PyStringMethods`] trait.
|
||||||
|
///
|
||||||
|
/// # Equality
|
||||||
|
///
|
||||||
|
/// For convenience, [`Bound<'py, PyString>`] implements [`PartialEq<str>`] to allow comparing the
|
||||||
|
/// data in the Python string to a Rust UTF-8 string slice.
|
||||||
|
///
|
||||||
|
/// This is not always the most appropriate way to compare Python strings, as Python string 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::PyString;
|
||||||
|
///
|
||||||
|
/// # Python::with_gil(|py| {
|
||||||
|
/// let py_string = PyString::new_bound(py, "foo");
|
||||||
|
/// // via PartialEq<str>
|
||||||
|
/// assert_eq!(py_string, "foo");
|
||||||
|
///
|
||||||
|
/// // via Python equality
|
||||||
|
/// assert!(py_string.as_any().eq("foo").unwrap());
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PyString(PyAny);
|
pub struct PyString(PyAny);
|
||||||
|
|
||||||
|
@ -490,6 +516,118 @@ impl IntoPy<Py<PyString>> for &'_ Py<PyString> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<str> for Bound<'_, PyString> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.as_borrowed() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<&'_ str> for Bound<'_, PyString> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
self.as_borrowed() == **other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<Bound<'_, PyString>> for str {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Bound<'_, PyString>) -> bool {
|
||||||
|
*self == other.as_borrowed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<&'_ Bound<'_, PyString>> for str {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &&Bound<'_, PyString>) -> bool {
|
||||||
|
*self == other.as_borrowed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<Bound<'_, PyString>> for &'_ str {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Bound<'_, PyString>) -> bool {
|
||||||
|
**self == other.as_borrowed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<str> for &'_ Bound<'_, PyString> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.as_borrowed() == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<str> for Borrowed<'_, '_, PyString> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
#[cfg(not(Py_3_13))]
|
||||||
|
{
|
||||||
|
self.to_cow().map_or(false, |s| s == other)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(Py_3_13)]
|
||||||
|
unsafe {
|
||||||
|
ffi::PyUnicode_EqualToUTF8AndSize(
|
||||||
|
self.as_ptr(),
|
||||||
|
other.as_ptr().cast(),
|
||||||
|
other.len() as _,
|
||||||
|
) == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<&str> for Borrowed<'_, '_, PyString> {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
*self == **other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<Borrowed<'_, '_, PyString>> for str {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Borrowed<'_, '_, PyString>) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares whether the data in the Python string is equal to the given UTF8.
|
||||||
|
///
|
||||||
|
/// In some cases Python equality might be more appropriate; see the note on [`PyString`].
|
||||||
|
impl PartialEq<Borrowed<'_, '_, PyString>> for &'_ str {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Borrowed<'_, '_, PyString>) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -708,15 +846,15 @@ mod tests {
|
||||||
fn test_intern_string() {
|
fn test_intern_string() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let py_string1 = PyString::intern_bound(py, "foo");
|
let py_string1 = PyString::intern_bound(py, "foo");
|
||||||
assert_eq!(py_string1.to_cow().unwrap(), "foo");
|
assert_eq!(py_string1, "foo");
|
||||||
|
|
||||||
let py_string2 = PyString::intern_bound(py, "foo");
|
let py_string2 = PyString::intern_bound(py, "foo");
|
||||||
assert_eq!(py_string2.to_cow().unwrap(), "foo");
|
assert_eq!(py_string2, "foo");
|
||||||
|
|
||||||
assert_eq!(py_string1.as_ptr(), py_string2.as_ptr());
|
assert_eq!(py_string1.as_ptr(), py_string2.as_ptr());
|
||||||
|
|
||||||
let py_string3 = PyString::intern_bound(py, "bar");
|
let py_string3 = PyString::intern_bound(py, "bar");
|
||||||
assert_eq!(py_string3.to_cow().unwrap(), "bar");
|
assert_eq!(py_string3, "bar");
|
||||||
|
|
||||||
assert_ne!(py_string1.as_ptr(), py_string3.as_ptr());
|
assert_ne!(py_string1.as_ptr(), py_string3.as_ptr());
|
||||||
});
|
});
|
||||||
|
@ -762,4 +900,34 @@ mod tests {
|
||||||
assert_eq!(py_string.to_string_lossy(py), "🐈 Hello <20><><EFBFBD>World");
|
assert_eq!(py_string.to_string_lossy(py), "🐈 Hello <20><><EFBFBD>World");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comparisons() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let s = "hello, world";
|
||||||
|
let py_string = PyString::new_bound(py, s);
|
||||||
|
|
||||||
|
assert_eq!(py_string, "hello, world");
|
||||||
|
|
||||||
|
assert_eq!(py_string, s);
|
||||||
|
assert_eq!(&py_string, s);
|
||||||
|
assert_eq!(s, py_string);
|
||||||
|
assert_eq!(s, &py_string);
|
||||||
|
|
||||||
|
assert_eq!(py_string, *s);
|
||||||
|
assert_eq!(&py_string, *s);
|
||||||
|
assert_eq!(*s, py_string);
|
||||||
|
assert_eq!(*s, &py_string);
|
||||||
|
|
||||||
|
let py_string = py_string.as_borrowed();
|
||||||
|
|
||||||
|
assert_eq!(py_string, s);
|
||||||
|
assert_eq!(&py_string, s);
|
||||||
|
assert_eq!(s, py_string);
|
||||||
|
assert_eq!(s, &py_string);
|
||||||
|
|
||||||
|
assert_eq!(py_string, *s);
|
||||||
|
assert_eq!(*s, py_string);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@ fn test_delattr() {
|
||||||
fn test_str() {
|
fn test_str() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let example_py = make_example(py);
|
let example_py = make_example(py);
|
||||||
assert_eq!(example_py.str().unwrap().to_cow().unwrap(), "5");
|
assert_eq!(example_py.str().unwrap(), "5");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,10 +139,7 @@ fn test_str() {
|
||||||
fn test_repr() {
|
fn test_repr() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let example_py = make_example(py);
|
let example_py = make_example(py);
|
||||||
assert_eq!(
|
assert_eq!(example_py.repr().unwrap(), "ExampleClass(value=5)");
|
||||||
example_py.repr().unwrap().to_cow().unwrap(),
|
|
||||||
"ExampleClass(value=5)"
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue