From 030692f7da54cfff2ab6297fc24822b7326de198 Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Sat, 19 Feb 2022 21:15:24 +0100 Subject: [PATCH 1/6] Added `is_eq`, `is_ne`, `is_gt` etc. methods for convenience and to be friendlier to beginners. Closes #2155. --- src/types/any.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/types/any.rs b/src/types/any.rs index b54aa540d34..457f56c2760 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -259,6 +259,66 @@ impl PyAny { } } + /// Tests whether this object is less than another. + /// + /// This is equivalent to the Python expression `self < other`. + pub fn is_lt(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Lt) + } + + /// Tests whether this object is less than or equal to another. + /// + /// This is equivalent to the Python expression `self <= other`. + pub fn is_le(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Le) + } + + /// Tests whether this object is equal to another. + /// + /// This is equivalent to the Python expression `self == other`. + pub fn is_eq(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Eq) + } + + /// Tests whether this object is not equal to another. + /// + /// This is equivalent to the Python expression `self != other`. + pub fn is_ne(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Ne) + } + + /// Tests whether this object is greater than another. + /// + /// This is equivalent to the Python expression `self > other`. + pub fn is_gt(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Gt) + } + + /// Tests whether this object is greater than or equal to another. + /// + /// This is equivalent to the Python expression `self >= other`. + pub fn is_ge(&self, other: O) -> PyResult<&PyAny> + where + O: ToPyObject, + { + self.rich_compare(other, CompareOp::Ge) + } + /// Determines whether this object appears callable. /// /// This is equivalent to Python's [`callable()`][1] function. From 9ebbfe28aa42f2b97aadd7dea375c33dd9955c69 Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Wed, 23 Feb 2022 20:28:14 +0100 Subject: [PATCH 2/6] Renamed `is_eq` to just `eq`, `is_ne` to `ne` and so on. --- src/types/any.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/types/any.rs b/src/types/any.rs index 457f56c2760..cd1448673ca 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -262,7 +262,7 @@ impl PyAny { /// Tests whether this object is less than another. /// /// This is equivalent to the Python expression `self < other`. - pub fn is_lt(&self, other: O) -> PyResult<&PyAny> + pub fn lt(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { @@ -272,7 +272,7 @@ impl PyAny { /// Tests whether this object is less than or equal to another. /// /// This is equivalent to the Python expression `self <= other`. - pub fn is_le(&self, other: O) -> PyResult<&PyAny> + pub fn le(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { @@ -282,7 +282,7 @@ impl PyAny { /// Tests whether this object is equal to another. /// /// This is equivalent to the Python expression `self == other`. - pub fn is_eq(&self, other: O) -> PyResult<&PyAny> + pub fn eq(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { @@ -292,7 +292,7 @@ impl PyAny { /// Tests whether this object is not equal to another. /// /// This is equivalent to the Python expression `self != other`. - pub fn is_ne(&self, other: O) -> PyResult<&PyAny> + pub fn ne(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { @@ -302,7 +302,7 @@ impl PyAny { /// Tests whether this object is greater than another. /// /// This is equivalent to the Python expression `self > other`. - pub fn is_gt(&self, other: O) -> PyResult<&PyAny> + pub fn gt(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { @@ -312,7 +312,7 @@ impl PyAny { /// Tests whether this object is greater than or equal to another. /// /// This is equivalent to the Python expression `self >= other`. - pub fn is_ge(&self, other: O) -> PyResult<&PyAny> + pub fn ge(&self, other: O) -> PyResult<&PyAny> where O: ToPyObject, { From f7ad52c75f57f3d672e3b34c2a8a63ce46bc2ba1 Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Thu, 24 Feb 2022 23:38:46 +0100 Subject: [PATCH 3/6] Added tests for the comparison shortcut methods . --- src/types/any.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/types/any.rs b/src/types/any.rs index cd1448673ca..067dfcf752c 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -769,7 +769,7 @@ mod tests { use crate::{ type_object::PyTypeObject, types::{IntoPyDict, PyList, PyLong, PyModule}, - Python, ToPyObject, + PyAny, PyResult, Python, ToPyObject, }; #[test] @@ -894,4 +894,83 @@ class SimpleClass: assert!(bad_haystack.contains(&irrelevant_needle).is_err()); }); } + + // This is intentionally not a test, it's a generic function used by the tests below. + fn test_eq_methods_generic(list: &[T]) + where + T: PartialEq + PartialOrd + ToPyObject, + { + Python::with_gil(|py| { + for a in list { + for b in list { + let a_py = a.to_object(py).into_ref(py); + let b_py = b.to_object(py).into_ref(py); + let unwrap_cmp = |cmp: PyResult<&PyAny>| cmp.unwrap().is_true().unwrap(); + assert_eq!( + a.lt(b), + unwrap_cmp(a_py.lt(b_py)), + "{a_py} should be less than {b_py}" + ); + assert_eq!( + a.le(b), + unwrap_cmp(a_py.le(b_py)), + "{a_py} should be less than or equal to {b_py}" + ); + assert_eq!( + a.eq(b), + unwrap_cmp(a_py.eq(b_py)), + "{a_py} should be equal to {b_py}" + ); + assert_eq!( + a.ne(b), + unwrap_cmp(a_py.ne(b_py)), + "{a_py} should not be equal to {b_py}" + ); + assert_eq!( + a.gt(b), + unwrap_cmp(a_py.gt(b_py)), + "{a_py} should be greater than {b_py}" + ); + assert_eq!( + a.ge(b), + unwrap_cmp(a_py.ge(b_py)), + "{a_py} should be greater than or equal to {b_py}" + ); + } + } + }); + } + + #[test] + fn test_eq_methods_integers() { + let ints = [-4, -4, 1, 2, 0, -100, 1_000_000]; + test_eq_methods_generic(&ints); + } + + #[test] + fn test_eq_methods_strings() { + let strings = ["Let's", "test", "some", "eq", "methods"]; + test_eq_methods_generic(&strings); + } + + #[test] + fn test_eq_methods_floats() { + let floats = [ + -1.0, + 2.5, + 0.0, + 3.0, + std::f64::consts::PI, + 10.0, + 10.0 / 3.0, + -1_000_000.0, + ]; + test_eq_methods_generic(&floats); + } + + #[test] + fn test_eq_methods_bools() { + let bools = [true, false]; + test_eq_methods_generic(&bools); + } } From 0ed4721ad0c3ac452cdce7fa498085f2b089b605 Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Fri, 25 Feb 2022 01:05:54 +0100 Subject: [PATCH 4/6] Logged changes in the changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c2cb6eebe2..8ab5e8d3719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `PyAny::contains` method (`in` operator for `PyAny`). [#2115](https://github.com/PyO3/pyo3/pull/2115) - Add `PyMapping::contains` method (`in` operator for `PyMapping`). [#2133](https://github.com/PyO3/pyo3/pull/2133) - Add garbage collection magic methods `__traverse__` and `__clear__` to `#[pymethods]`. [#2159](https://github.com/PyO3/pyo3/pull/2159) +- Add `eq`, `ne`, `lt`, `le`, `gt`, `ge` methods to `PyAny` that wrap `rich_compare`. ### Changed From 9ddcefe66f2d6ad5b4b9d81efe63b385ca7bb7ae Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Fri, 25 Feb 2022 16:37:56 +0100 Subject: [PATCH 5/6] Switched the return type for the `PyAny::eq`, `PyAny::ne` etc. functions to `PyResult`. --- src/types/any.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/types/any.rs b/src/types/any.rs index 067dfcf752c..d6acb819afd 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -262,61 +262,61 @@ impl PyAny { /// Tests whether this object is less than another. /// /// This is equivalent to the Python expression `self < other`. - pub fn lt(&self, other: O) -> PyResult<&PyAny> + pub fn lt(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Lt) + self.rich_compare(other, CompareOp::Lt)?.is_true() } /// Tests whether this object is less than or equal to another. /// /// This is equivalent to the Python expression `self <= other`. - pub fn le(&self, other: O) -> PyResult<&PyAny> + pub fn le(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Le) + self.rich_compare(other, CompareOp::Le)?.is_true() } /// Tests whether this object is equal to another. /// /// This is equivalent to the Python expression `self == other`. - pub fn eq(&self, other: O) -> PyResult<&PyAny> + pub fn eq(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Eq) + self.rich_compare(other, CompareOp::Eq)?.is_true() } /// Tests whether this object is not equal to another. /// /// This is equivalent to the Python expression `self != other`. - pub fn ne(&self, other: O) -> PyResult<&PyAny> + pub fn ne(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Ne) + self.rich_compare(other, CompareOp::Ne)?.is_true() } /// Tests whether this object is greater than another. /// /// This is equivalent to the Python expression `self > other`. - pub fn gt(&self, other: O) -> PyResult<&PyAny> + pub fn gt(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Gt) + self.rich_compare(other, CompareOp::Gt)?.is_true() } /// Tests whether this object is greater than or equal to another. /// /// This is equivalent to the Python expression `self >= other`. - pub fn ge(&self, other: O) -> PyResult<&PyAny> + pub fn ge(&self, other: O) -> PyResult where O: ToPyObject, { - self.rich_compare(other, CompareOp::Ge) + self.rich_compare(other, CompareOp::Ge)?.is_true() } /// Determines whether this object appears callable. From 7285927736053574d471d8d1e714e9dfb22a6384 Mon Sep 17 00:00:00 2001 From: Tommaso Thea Cioni Date: Fri, 25 Feb 2022 17:02:27 +0100 Subject: [PATCH 6/6] Updated tests to reflect changes in 9ddcefe66f2d6ad5b4b9d81efe63b385ca7bb7ae. --- src/types/any.rs | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/types/any.rs b/src/types/any.rs index d6acb819afd..fb5d9b8ad2d 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -769,9 +769,8 @@ mod tests { use crate::{ type_object::PyTypeObject, types::{IntoPyDict, PyList, PyLong, PyModule}, - PyAny, PyResult, Python, ToPyObject, + Python, ToPyObject, }; - #[test] fn test_call_for_non_existing_method() { Python::with_gil(|py| { @@ -905,36 +904,54 @@ class SimpleClass: for b in list { let a_py = a.to_object(py).into_ref(py); let b_py = b.to_object(py).into_ref(py); - let unwrap_cmp = |cmp: PyResult<&PyAny>| cmp.unwrap().is_true().unwrap(); + assert_eq!( a.lt(b), - unwrap_cmp(a_py.lt(b_py)), - "{a_py} should be less than {b_py}" + a_py.lt(b_py).unwrap(), + "{} < {} should be {}.", + a_py, + b_py, + a.lt(b) ); assert_eq!( a.le(b), - unwrap_cmp(a_py.le(b_py)), - "{a_py} should be less than or equal to {b_py}" + a_py.le(b_py).unwrap(), + "{} <= {} should be {}.", + a_py, + b_py, + a.le(b) ); assert_eq!( a.eq(b), - unwrap_cmp(a_py.eq(b_py)), - "{a_py} should be equal to {b_py}" + a_py.eq(b_py).unwrap(), + "{} == {} should be {}.", + a_py, + b_py, + a.eq(b) ); assert_eq!( a.ne(b), - unwrap_cmp(a_py.ne(b_py)), - "{a_py} should not be equal to {b_py}" + a_py.ne(b_py).unwrap(), + "{} != {} should be {}.", + a_py, + b_py, + a.ne(b) ); assert_eq!( a.gt(b), - unwrap_cmp(a_py.gt(b_py)), - "{a_py} should be greater than {b_py}" + a_py.gt(b_py).unwrap(), + "{} > {} should be {}.", + a_py, + b_py, + a.gt(b) ); assert_eq!( a.ge(b), - unwrap_cmp(a_py.ge(b_py)), - "{a_py} should be greater than or equal to {b_py}" + a_py.ge(b_py).unwrap(), + "{} >= {} should be {}.", + a_py, + b_py, + a.ge(b) ); } }