Cython-like rich comparisons in Python¶
With “rich comparisons”, we mean the Python 3 comparisons which are
usually implemented in Python using methods like __eq__ and
__lt__. Internally in Python, there is only one rich comparison
slot tp_richcompare. The actual operator is passed as an integer
constant (defined in this module as
op_LT, op_LE, op_EQ, op_NE, op_GT, op_GE).
Cython exposes rich comparisons in cdef classes as the
__richcmp__ special method. The Sage coercion model also supports
rich comparisons this way: for two instances x and y
of Element, x._richcmp_(y, op)
is called when the user does something like x <= y
(possibly after coercion if x and y have different parents).
Various helper functions exist to make it easier to implement rich
comparison: the most important one is the richcmp() function.
This is analogous to the Python 2 function cmp() but implements
rich comparison, with the comparison operator (e.g. op_GE) as
third argument. There is also richcmp_not_equal() which is like
richcmp() but it is optimized assuming that the compared objects
are not equal.
The functions rich_to_bool() and rich_to_bool_sgn() can be
used to convert results of cmp() (i.e. -1, 0 or 1) to a boolean
True/False for rich comparisons.
AUTHORS:
- Jeroen Demeyer
-
sage.structure.richcmp.revop(op)¶ Return the reverse operation of
op.For example, <= becomes >=, etc.
EXAMPLES:
sage: from sage.structure.richcmp import revop sage: [revop(i) for i in range(6)] [4, 5, 2, 3, 0, 1]
-
sage.structure.richcmp.rich_to_bool(op, c)¶ Return the corresponding
TrueorFalsevalue for a rich comparison, given the result of an old-style comparison.INPUT:
op– a rich comparison operation (e.g.Py_EQ)c– the result of an old-style comparison: -1, 0 or 1.
OUTPUT: 1 or 0 (corresponding to
TrueandFalse)See also
rich_to_bool_sgn()ifccould be outside the [-1, 0, 1] range.EXAMPLES:
sage: from sage.structure.richcmp import (rich_to_bool, ....: op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE) sage: for op in (op_LT, op_LE, op_EQ, op_NE, op_GT, op_GE): ....: for c in (-1,0,1): ....: print(rich_to_bool(op, c)) True False False True True False False True False True False True False False True False True True
Indirect tests using integers:
sage: 0 < 5, 5 < 5, 5 < -8 (True, False, False) sage: 0 <= 5, 5 <= 5, 5 <= -8 (True, True, False) sage: 0 >= 5, 5 >= 5, 5 >= -8 (False, True, True) sage: 0 > 5, 5 > 5, 5 > -8 (False, False, True) sage: 0 == 5, 5 == 5, 5 == -8 (False, True, False) sage: 0 != 5, 5 != 5, 5 != -8 (True, False, True)
-
sage.structure.richcmp.rich_to_bool_sgn(op, c)¶ Same as
rich_to_bool, but allow any \(c < 0\) and \(c > 0\) instead of only \(-1\) and \(1\).Note
This is in particular needed for
mpz_cmp().
-
sage.structure.richcmp.richcmp(x, y, op)¶ Return the result of the rich comparison of
xandywith operatorop.INPUT:
x,y– arbitrary Python objectsop– comparison operator (one ofop_LT`, ``op_LE,op_EQ,op_NE,op_GT,op_GE).
EXAMPLES:
sage: from sage.structure.richcmp import * sage: richcmp(3, 4, op_LT) True sage: richcmp(x, x^2, op_EQ) x == x^2
The two examples above are completely equivalent to
3 < 4andx == x^2. For this reason, it only makes sense in practice to callrichcmpwith a non-constant value forop.We can write a custom
Elementclass which shows a more realistic example of how to use this:sage: from sage.structure.element import Element sage: class MyElement(Element): ....: def __init__(self, parent, value): ....: Element.__init__(self, parent) ....: self.v = value ....: def _richcmp_(self, other, op): ....: return richcmp(self.v, other.v, op) sage: P = Parent() sage: x = MyElement(P, 3) sage: y = MyElement(P, 3) sage: x < y False sage: x == y True sage: x > y False
-
sage.structure.richcmp.richcmp_by_eq_and_lt(eq_attr, lt_attr)¶ Create a rich comparison method for a partial order, where the order is specified by methods called
eq_attrandlt_attr.INPUT when creating the method:
eq_attr– attribute name for equality comparisonlt_attr– attribute name for less-than comparison
INPUT when calling the method:
self– objects having methodseq_attrandlt_attrother– arbitrary object. If it does haveeq_attrandlt_attrmethods, these are used for the comparison. Otherwise, the comparison is undefined.op– a rich comparison operation (e.g.op_EQ)
Note
For efficiency, identical objects (when
self is other) always compare equal.Note
The order is partial, so
x <= yis implemented asx == y or x < y. It is not required that this is the negation ofy < x.Note
This function is intended to be used as a method
_richcmp_in a class derived fromsage.structure.element.Elementor a method__richcmp__in a class usingrichcmp_method().EXAMPLES:
sage: from sage.structure.richcmp import richcmp_by_eq_and_lt sage: from sage.structure.element import Element sage: class C(Element): ....: def __init__(self, a, b): ....: super(C, self).__init__(ZZ) ....: self.a = a ....: self.b = b ....: _richcmp_ = richcmp_by_eq_and_lt("eq", "lt") ....: def eq(self, other): ....: return self.a == other.a and self.b == other.b ....: def lt(self, other): ....: return self.a < other.a and self.b < other.b sage: x = C(1,2); y = C(2,1); z = C(3,3) sage: x == x, x <= x, x == C(1,2), x <= C(1,2) # indirect doctest (True, True, True, True) sage: y == z, y != z (False, True) sage: x < y, y < x, x > y, y > x, x <= y, y <= x, x >= y, y >= x (False, False, False, False, False, False, False, False) sage: y < z, z < y, y > z, z > y, y <= z, z <= y, y >= z, z >= y (True, False, False, True, True, False, False, True) sage: z < x, x < z, z > x, x > z, z <= x, x <= z, z >= x, x >= z (False, True, True, False, False, True, True, False)
A simple example using
richcmp_method:sage: from sage.structure.richcmp import richcmp_method, richcmp_by_eq_and_lt sage: @richcmp_method ....: class C(object): ....: __richcmp__ = richcmp_by_eq_and_lt("_eq", "_lt") ....: def _eq(self, other): ....: return True ....: def _lt(self, other): ....: return True sage: a = C(); b = C() sage: a == b True sage: a > b # Calls b._lt(a) True sage: class X(object): pass sage: x = X() sage: a == x # Does not call a._eq(x) because x does not have _eq False
-
sage.structure.richcmp.richcmp_method(cls)¶ Class decorator to implement rich comparions using the special method
__richcmp__(analogous to Cython) instead of the 6 methods__eq__and friends.This changes the class in-place and returns the given class.
EXAMPLES:
sage: from sage.structure.richcmp import * sage: sym = {op_EQ: "==", op_NE: "!=", op_LT: "<", op_GT: ">", op_LE: "<=", op_GE: ">="} sage: @richcmp_method ....: class A(str): ....: def __richcmp__(self, other, op): ....: print("%s %s %s" % (self, sym[op], other)) sage: A("left") < A("right") left < right sage: object() <= A("right") right >= <object object at ...>
We can call this comparison with the usual Python special methods:
sage: x = A("left"); y = A("right") sage: x.__eq__(y) left == right sage: A.__eq__(x, y) left == right
Everything still works in subclasses:
sage: class B(A): ....: pass sage: x = B("left"); y = B("right") sage: x != y left != right sage: x.__ne__(y) left != right sage: B.__ne__(x, y) left != right
We can override
__richcmp__with standard Python rich comparison methods and conversely:sage: class C(A): ....: def __ne__(self, other): ....: return False sage: C("left") != C("right") False sage: C("left") == C("right") # Calls __eq__ from class A left == right sage: class Base(object): ....: def __eq__(self, other): ....: return False sage: @richcmp_method ....: class Derived(Base): ....: def __richcmp__(self, other, op): ....: return True sage: Derived() == Derived() True
-
sage.structure.richcmp.richcmp_not_equal(x, y, op)¶ Like
richcmp(x, y, op)but assuming that \(x\) is not equal to \(y\).INPUT:
op– a rich comparison operation (e.g.Py_EQ)
OUTPUT:
If
opis notop_EQorop_NE, the result ofrichcmp(x, y, op). Ifopisop_EQ, returnFalse. Ifopisop_NE, returnTrue.This is useful to compare lazily two objects A and B according to 2 (or more) different parameters, say width and height for example. One could use:
return richcmp((A.width(), A.height()), (B.width(), B.height()), op)
but this will compute both width and height in all cases, even if A.width() and B.width() are enough to decide the comparison.
Instead one can do:
wA = A.width() wB = B.width() if wA != wB: return richcmp_not_equal(wA, wB, op) return richcmp(A.height(), B.height(), op)
The difference with
richcmpis thatrichcmp_not_equalassumes that its arguments are not equal, which is excluding the case where the comparison cannot be decided so far, without knowing the rest of the parameters.EXAMPLES:
sage: from sage.structure.richcmp import (richcmp_not_equal, ....: op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE) sage: for op in (op_LT, op_LE, op_EQ, op_NE, op_GT, op_GE): ....: print(richcmp_not_equal(3, 4, op)) True True False True False False sage: for op in (op_LT, op_LE, op_EQ, op_NE, op_GT, op_GE): ....: print(richcmp_not_equal(5, 4, op)) False False False True True True