diff -r 366ee0f84118 Doc/reference/expressions.rst --- a/Doc/reference/expressions.rst Mon Jul 20 21:35:38 2015 -0700 +++ b/Doc/reference/expressions.rst Wed Jul 22 02:28:44 2015 +0000 @@ -1036,10 +1036,6 @@ .. _comparisons: -.. _is: -.. _is not: -.. _in: -.. _not in: Comparisons =========== @@ -1075,66 +1071,182 @@ *c*, so that, e.g., ``x < y > z`` is perfectly legal (though perhaps not pretty). +Value comparisons +----------------- + The operators ``<``, ``>``, ``==``, ``>=``, ``<=``, and ``!=`` compare the -values of two objects. The objects need not have the same type. If both are -numbers, they are converted to a common type. Otherwise, the ``==`` and ``!=`` -operators *always* consider objects of different types to be unequal, while the -``<``, ``>``, ``>=`` and ``<=`` operators raise a :exc:`TypeError` when -comparing objects of different types that do not implement these operators for -the given pair of types. You can control comparison behavior of objects of -non-built-in types by defining rich comparison methods like :meth:`__gt__`, -described in section :ref:`customization`. +values of two objects. The objects do not need to have the same type. -Comparison of objects of the same type depends on the type: +Chapter :ref:`objects` states that objects have a value (in addition to type +and identity). The value of an object is a rather abstract notion in Python: +For example, there is no canonical access method for an object's value. Also, +there is no requirement that the value of an object should be constructed in a +particular way, e.g. comprised of all its data attributes. Comparison operators +implement a particular notion of what the value of an object is. One can think +of them as defining the value of an object indirectly, by means of their +comparison implementation. -* Numbers are compared arithmetically. +Because all types are (direct or indirect) subtypes of :class:`object`, they +inherit the default comparison behavior from :class:`object`. Types can +customize their comparison behavior by implementing +:dfn:`rich comparison methods` like :meth:`__lt__`, described in +:ref:`customization`. -* The values :const:`float('NaN')` and :const:`Decimal('NaN')` are special. - They are identical to themselves, ``x is x`` but are not equal to themselves, - ``x != x``. Additionally, comparing any value to a not-a-number value +The default behavior for equality comparison (``==`` and ``!=``) is based on +the identity of the objects. Hence, equality comparison of instances with the +same identity results in equality, and equality comparison of instances with +different identities results in inequality. A motivation for this default +behavior is the desire that all objects should be reflexive (i.e. ``x is y`` +implies ``x == y``). + +A default order comparison (``<``, ``>``, ``<=``, and ``>=``) is not provided; +an attempt raises :exc:`TypeError`. A motivation for this default behavior is +the lack of a similar invariant as for equality. + +The behavior of the default equality comparison, that instances with different +identities are always unequal, may be in contrast to what types will need that +have a sensible definition of object value and value-based equality. Such +types will need to customize their comparison behavior, and in fact, a number +of built-in types have done that. + +The following list describes the comparison behavior of the most important +built-in types. + +* Numbers of built-in numeric types (:ref:`typesnumeric`) and of the standard + library types :class:`fractions.Fraction` and :class:`decimal.Decimal` can be + compared within and across their types, with the restriction that complex + numbers do not support order comparison. Within the limits of the types + involved, they compare mathematically (algorithmically) correct without loss + of precision. + + The not-a-number values :const:`float('NaN')` and :const:`Decimal('NaN')` + are special. They are identical to themselves (``x is x`` is true) but + are not equal to themselves (``x == x`` is false). Additionally, + comparing any number to a not-a-number value will return ``False``. For example, both ``3 < float('NaN')`` and ``float('NaN') < 3`` will return ``False``. -* Bytes objects are compared lexicographically using the numeric values of their - elements. +* Binary sequences (instances of :class:`bytes` or :class:`bytearray`) can be + compared within and across their types. They compare lexicographically using + the numeric values of their elements. -* Strings are compared lexicographically using the numeric equivalents (the - result of the built-in function :func:`ord`) of their characters. [#]_ String - and bytes object can't be compared! +* Strings (instances of :class:`str`) compare lexicographically using the + numerical Unicode code points (the result of the built-in function + :func:`ord`) of their characters. [#]_ -* Tuples and lists are compared lexicographically using comparison of - corresponding elements. This means that to compare equal, each element must - compare equal and the two sequences must be of the same type and have the same - length. + Strings and binary sequences cannot be directly compared. - If not equal, the sequences are ordered the same as their first differing - elements. For example, ``[1,2,x] <= [1,2,y]`` has the same value as - ``x <= y``. If the corresponding element does not exist, the shorter - sequence is ordered first (for example, ``[1,2] < [1,2,3]``). +* Sequences (instances of :class:`tuple`, :class:`list`, or :class:`range`) can + be compared only within each of their types, with the restriction that ranges + do not support order comparison. Equality comparison across these types + results in unequality, and ordering comparison across these types raises + :exc:`TypeError`. -* Mappings (dictionaries) compare equal if and only if they have the same - ``(key, value)`` pairs. Order comparisons ``('<', '<=', '>=', '>')`` - raise :exc:`TypeError`. + Sequences compare lexicographically using comparison of corresponding + elements, whereby reflexivity of the elements is enforced. -* Sets and frozensets define comparison operators to mean subset and superset - tests. Those relations do not define total orderings (the two sets ``{1,2}`` - and ``{2,3}`` are not equal, nor subsets of one another, nor supersets of one + In enforcing reflexivity of elements, the comparison of collections assumes + that for a collection element ``x``, ``x == x`` is always true. Based on + that assumption, element identity is compared first, and element comparison + is performed only for distinct elements. This approach yields the same + result as a strict element comparison would, if the compared elements are + reflexive. For non-reflexive elements, the result is different than for + strict element comparison, and may be surprising: The non-reflexive + not-a-number values for example result in the following comparison behavior + when used in a list:: + + >>> math.nan is math.nan + True + >>> math.nan == math.nan + False <-- the defined non-reflexive behavior of NaN + >>> [math.nan] == [math.nan] + True <-- list enforces reflexivity and tests identity first + + Lexicographical comparison between built-in collections works as follows: + + - For two collections to compare equal, they must be of the same type, have + the same length, and each pair of corresponding elements must compare + equal (for example, ``[1,2] == (1,2)`` is false because the type is not the + same). + + - Collections that support order comparison are ordered the same as their + first unequal elements (for example, ``[1,2,x] <= [1,2,y]`` has the same + value as ``x <= y``). If a corresponding element does not exist, the + shorter collection is ordered first (for example, ``[1,2] < [1,2,3]`` is + true). + +* Mappings (instances of :class:`dict`) compare equal if and only if they have + equal `(key, value)` pairs. Equality comparison of the keys and elements + enforces reflexivity. + + Order comparisons (``<``, ``>``, ``<=``, and ``>=``) raise :exc:`TypeError`. + +* Sets (instances of :class:`set` or :class:`frozenset`) can be compared within + and across their types. + + They define order + comparison operators to mean subset and superset tests. Those relations do + not define total orderings (for example, the two sets ``{1,2}`` and ``{2,3}`` + are not equal, nor subsets of one another, nor supersets of one another). Accordingly, sets are not appropriate arguments for functions - which depend on total ordering. For example, :func:`min`, :func:`max`, and - :func:`sorted` produce undefined results given a list of sets as inputs. + which depend on total ordering (for example, :func:`min`, :func:`max`, and + :func:`sorted` produce undefined results given a list of sets as inputs). -* Most other objects of built-in types compare unequal unless they are the same - object; the choice whether one object is considered smaller or larger than - another one is made arbitrarily but consistently within one execution of a - program. + Comparison of sets enforces reflexivity of its elements. -Comparison of objects of differing types depends on whether either of the -types provide explicit support for the comparison. Most numeric types can be -compared with one another. When cross-type comparison is not supported, the -comparison method returns ``NotImplemented``. +* Most other built-in types have no comparison methods implemented, so they + inherit the default comparison behavior. +User-defined classes that customize their comparison behavior should follow +some consistency rules, if possible: + +* Equality comparison should be reflexive. + In other words, identical objects should compare equal: + + ``x is y`` implies ``x == y`` + +* Comparison should be symmetric. + In other words, the following expressions should have the same result: + + ``x == y`` and ``y == x`` + + ``x != y`` and ``y != x`` + + ``x < y`` and ``y > x`` + + ``x <= y`` and ``y >= x`` + +* Comparison should be transitive. + The following (non-exhaustive) examples illustrate that: + + ``x > y and y > z`` implies ``x > z`` + + ``x < y and y <= z`` implies ``x < z`` + +* Inverse comparison should result in the boolean negation. + In other words, the following expressions should have the same result: + + ``x == y`` and ``not x != y`` + + ``x < y`` and ``not x >= y`` (for total ordering) + + ``x > y`` and ``not x <= y`` (for total ordering) + + The last two expressions apply to totally ordered collections (e.g. to + sequences, but not to sets or mappings). See also the + :func:`~functools.total_ordering` decorator. + +Python does not enforce these consistency rules. In fact, the not-a-number +values are an example for not following these rules. + + +.. _in: +.. _not in: .. _membership-test-details: +Membership test operations +-------------------------- + The operators :keyword:`in` and :keyword:`not in` test for membership. ``x in s`` evaluates to true if *x* is a member of *s*, and false otherwise. ``x not in s`` returns the negation of ``x in s``. All built-in sequences and set types @@ -1176,6 +1288,13 @@ operator: is not pair: identity; test + +.. _is: +.. _is not: + +Identity comparisons +-------------------- + The operators :keyword:`is` and :keyword:`is not` test for object identity: ``x is y`` is true if and only if *x* and *y* are the same object. ``x is not y`` yields the inverse truth value. [#]_ @@ -1405,12 +1524,24 @@ cases, Python returns the latter result, in order to preserve that ``divmod(x,y)[0] * y + x % y`` be very close to ``x``. -.. [#] While comparisons between strings make sense at the byte level, they may - be counter-intuitive to users. For example, the strings ``"\u00C7"`` and - ``"\u0043\u0327"`` compare differently, even though they both represent the - same unicode character (LATIN CAPITAL LETTER C WITH CEDILLA). To compare - strings in a human recognizable way, compare using - :func:`unicodedata.normalize`. +.. [#] The Unicode standard distinguishes between :dfn:`code points` + (e.g. U+0041) and :dfn:`abstract characters` (e.g. "LATIN CAPITAL LETTER A"). + While most abstract characters in Unicode are only represented using one + code point, there is a number of abstract characters that can in addition be + represented using a sequence of more than one code point. For example, the + abstract character "LATIN CAPITAL LETTER C WITH CEDILLA" can be represented + as a single :dfn:`precomposed character` at code position U+00C7, or as a + sequence of a :dfn:`base character` at code position U+0043 (LATIN CAPITAL + LETTER C), followed by a :dfn:`combining character` at code position U+0327 + (COMBINING CEDILLA). + + The comparison operators on strings compare at the level of Unicode code + points. This may be counter-intuitive to humans. For example, + ``"\u00C7" == "\u0043\u0327"`` is ``False``, even though both strings + represent the same abstract character "LATIN CAPITAL LETTER C WITH CEDILLA". + + To compare strings at the level of abstract characters (that is, in a way + intuitive to humans), use :func:`unicodedata.normalize`. .. [#] Due to automatic garbage-collection, free lists, and the dynamic nature of descriptors, you may notice seemingly unusual behaviour in certain uses of diff -r 366ee0f84118 Lib/test/test_compare.py --- a/Lib/test/test_compare.py Mon Jul 20 21:35:38 2015 -0700 +++ b/Lib/test/test_compare.py Wed Jul 22 02:28:44 2015 +0000 @@ -1,27 +1,39 @@ +""" +Test equality and order comparisons. +""" + import unittest +from fractions import Fraction +from decimal import Decimal -class Empty: - def __repr__(self): - return '' -class Cmp: - def __init__(self,arg): - self.arg = arg +class ComparisonSimpleTest(unittest.TestCase): + """ + A testcase that verifies the behavior of equality and order comparisons for + some simple cases. + """ - def __repr__(self): - return '' % self.arg + class Empty: + def __repr__(self): + return '' - def __eq__(self, other): - return self.arg == other + class Cmp: + def __init__(self, arg): + self.arg = arg -class Anything: - def __eq__(self, other): - return True + def __repr__(self): + return '' % self.arg - def __ne__(self, other): - return False + def __eq__(self, other): + return self.arg == other -class ComparisonTest(unittest.TestCase): + class Anything: + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + set1 = [2, 2.0, 2, 2+0j, Cmp(2.0)] set2 = [[1], (3,), None, Empty()] candidates = set1 + set2 @@ -38,16 +50,15 @@ # Ensure default comparison compares id() of args L = [] for i in range(10): - L.insert(len(L)//2, Empty()) + L.insert(len(L)//2, self.Empty()) for a in L: for b in L: - self.assertEqual(a == b, id(a) == id(b), - 'a=%r, b=%r' % (a, b)) + self.assertEqual(a == b, a is b, 'a=%r, b=%r' % (a, b)) def test_ne_defaults_to_not_eq(self): - a = Cmp(1) - b = Cmp(1) - c = Cmp(2) + a = self.Cmp(1) + b = self.Cmp(1) + c = self.Cmp(2) self.assertIs(a == b, True) self.assertIs(a != b, False) self.assertIs(a != c, True) @@ -113,11 +124,674 @@ def test_issue_1393(self): x = lambda: None - self.assertEqual(x, Anything()) - self.assertEqual(Anything(), x) + self.assertEqual(x, self.Anything()) + self.assertEqual(self.Anything(), x) y = object() - self.assertEqual(y, Anything()) - self.assertEqual(Anything(), y) + self.assertEqual(y, self.Anything()) + self.assertEqual(self.Anything(), y) + + +class ComparisonFullTest(unittest.TestCase): + """ + A testcase that verifies the behavior of equality and ordering + comparisons for built-in types and user-defined classes that implement + relevant combinations of rich comparison methods. + """ + + + class CompBase(object): + """ + Base class for classes with rich comparison methods. + + The "x" attribute should be set to an underlying value to compare. + """ + meth = () # names of comparison methods implemented here. See + # assert_comparisons(). + + + # Class without any rich comparison methods + + class CompNone(CompBase): + pass + + + # Classes with all combinations of value-based equality comparison methods + + class CompEq(CompBase): + meth = ("eq",) + def __eq__(self, other): + return self.x == other.x + + class CompNe(CompBase): + meth = ("ne",) + def __ne__(self, other): + return self.x != other.x + + class CompEqNe(CompBase): + meth = ("eq", "ne") + def __eq__(self, other): + return self.x == other.x + def __ne__(self, other): + return self.x != other.x + + + # Classes with all combinations of value-based less/greater-than order + # comparison methods + + class CompLt(CompBase): + meth = ("lt",) + def __lt__(self, other): + return self.x < other.x + + class CompGt(CompBase): + meth = ("gt",) + def __gt__(self, other): + return self.x > other.x + + class CompLtGt(CompBase): + meth = ("lt", "gt") + def __lt__(self, other): + return self.x < other.x + def __gt__(self, other): + return self.x > other.x + + + # Classes with all combinations of value-based less/greater-or-equal-than + # order comparison methods + + class CompLe(CompBase): + meth = ("le",) + def __le__(self, other): + return self.x <= other.x + + class CompGe(CompBase): + meth = ("ge",) + def __ge__(self, other): + return self.x >= other.x + + class CompLeGe(CompBase): + meth = ("le", "ge") + def __le__(self, other): + return self.x <= other.x + def __ge__(self, other): + return self.x >= other.x + + + # It should be sufficient to combine the comparison methods only within + # each group: + all_comp_classes = (CompNone, + CompEq, CompNe, CompEqNe, # equal group + CompLt, CompGt, CompLtGt, # less/greater-than group + CompLe, CompGe, CompLeGe) # less/greater-or-equal group + + + def create_sorted_insts(self, class_, values): + """ + Create a number of objects of type `class_` and return them in a list. + + `values` is a list of values that determines the value of data + attribute `x` of each object. + + The main feature of this function is that the objects in the result + list are sorted by their identity, and afterwards the values of the + `values` list are assigned to these objects. Testcases can utilize this + to assign decreasing values to objects with increasing identities, + which in turn allows asserting that order comparison is performed by + value and not by identity. + """ + + # create a list of instances with the default constructor + insts = [] + for i in range(0, len(values)): + insts.append(class_()) + + # sort the instance list by identity + insts.sort(key=id) + + # assign the provided values to the instances + i = 0 + for inst in insts: + inst.x = values[i] + i += 1 + + return insts + + + def assert_equality_only(self, a, b, equal): + """ + Assert equality result and that ordering is not implemented. + + a, b: Instances to be tested (of same or different type). + + equal: Boolean indicating the expected equality comparison result: + True means: a == b + False means: a != b + """ + self.assertEqual(a == b, equal) + self.assertEqual(b == a, equal) + self.assertEqual(a != b, not equal) + self.assertEqual(b != a, not equal) + with self.assertRaisesRegex(TypeError, "unorderable"): + a < b + with self.assertRaisesRegex(TypeError, "unorderable"): + a <= b + with self.assertRaisesRegex(TypeError, "unorderable"): + a > b + with self.assertRaisesRegex(TypeError, "unorderable"): + a >= b + with self.assertRaisesRegex(TypeError, "unorderable"): + b < a + with self.assertRaisesRegex(TypeError, "unorderable"): + b <= a + with self.assertRaisesRegex(TypeError, "unorderable"): + b > a + with self.assertRaisesRegex(TypeError, "unorderable"): + b >= a + + + def assert_total_order(self, a, b, comp, a_meth=None, b_meth=None): + """ + Perform assertions on total ordering comparison of two instances. + + This function implements the knowledge about how equality and ordering + comparisons are supposed to work. + + a, b: Instances to be tested (of same or different type). + + comp: Integer indicating the expected order comparison result, + for operations that are supported by the classes: + <0 means: a < b + 0 means: a == b + >0 means: a > b + + a_meth, b_meth: Tuple of rich comparison method names (without + leading and trailing underscores) that are expected to be + available for the corresponding instance. This information + is only needed for instances of user-defined classes; the + knowledge about comparison methods for built-in types is + covered in is_value_comparable(). + Possible values for the tuple items are: + "eq", "ne", "lt", "le", "gt", "ge". + """ + + args = dict(a=a, b=b, comp=comp, a_meth=a_meth, b_meth=b_meth) + self.assert_eq_comparison_subtest(**args) + self.assert_ne_comparison_subtest(**args) + self.assert_lt_comparison_subtest(**args) + self.assert_le_comparison_subtest(**args) + self.assert_gt_comparison_subtest(**args) + self.assert_ge_comparison_subtest(**args) + + def assert_eq_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test "==" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or "eq" in a_meth \ + or "eq" in b_meth: + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a == b, comp == 0) + self.assertEqual(b == a, comp == 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + self.assertEqual(a == b, a is b) + self.assertEqual(b == a, a is b) + + def assert_ne_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test "!=" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or not {"ne", "eq"}.isdisjoint(a_meth + b_meth): + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a != b, comp != 0) + self.assertEqual(b != a, comp != 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + self.assertEqual(a != b, a is not b) + self.assertEqual(b != a, a is not b) + + def assert_lt_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test "<" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or "lt" in a_meth \ + or "gt" in b_meth: + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a < b, comp < 0) + self.assertEqual(b > a, comp < 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + with self.assertRaises(TypeError): + a < b + with self.assertRaises(TypeError): + b > a + + def assert_le_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test "<=" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or "le" in a_meth \ + or "ge" in b_meth: + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a <= b, comp <= 0) + self.assertEqual(b >= a, comp <= 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + with self.assertRaises(TypeError): + a <= b + with self.assertRaises(TypeError): + b >= a + + def assert_gt_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test ">" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or "gt" in a_meth \ + or "lt" in b_meth: + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a > b, comp > 0) + self.assertEqual(b < a, comp > 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + with self.assertRaises(TypeError): + a > b + with self.assertRaises(TypeError): + b < a + + def assert_ge_comparison_subtest(self, *, a, b, comp, a_meth, b_meth): + """ Test ">=" comparison. + The comparison is performed in both directions of the operands. + """ + + if a_meth is None or "ge" in a_meth \ + or "le" in b_meth: + # There are value-based comparison methods; + # we expect what the testcase defined + self.assertEqual(a >= b, comp >= 0) + self.assertEqual(b <= a, comp >= 0) + else: + # There are no value-based comparison methods; + # we expect the default behavior of object + with self.assertRaises(TypeError): + a >= b + with self.assertRaises(TypeError): + b <= a + + + def test_objects(self): + """ + Test comparison for two instances of type 'object'. + """ + + a = object() + b = object() + + self.assert_equality_only(a, a, True) + self.assert_equality_only(a, b, False) + + + def test_comp_classes(self): + """ + Test comparison for instances that have different combinations of + comparison methods implemented. + + This test function tests all combinations of those classes, so that + instances are sometimes of different classes and sometimes of the same + class. + """ + + # Comparisons of objects of the same class. + + for cls in self.all_comp_classes: + with self.subTest(cls): + insts = self.create_sorted_insts(cls, (1, 2, 1)) + + # same object + self.assert_total_order(insts[0], insts[0], 0, + cls.meth, cls.meth) + + # different objects, same value + self.assert_total_order(insts[0], insts[2], 0, + cls.meth, cls.meth) + + # different objects, value ascending for ascending identities + self.assert_total_order(insts[0], insts[1], -1, + cls.meth, cls.meth) + + # different objects, value descending for ascending identities. + # This is the interesting case to assert that order comparison + # is performed based on the value and not based on the identity. + self.assert_total_order(insts[1], insts[2], +1, + cls.meth, cls.meth) + + # Comparisons of objects of the combination of all classes. + + for cls_a in self.all_comp_classes: + + for cls_b in self.all_comp_classes: + with self.subTest(a=cls_a, b=cls_b): + a1 = cls_a() + a1.x = 1 + b1 = cls_b() + b1.x = 1 + b2 = cls_b() + b2.x = 2 + + # different objects, same value + self.assert_total_order( + a1, b1, 0, cls_a.meth, cls_b.meth) + + self.assert_total_order( + a1, b2, -1, cls_a.meth, cls_b.meth) + + + def test_str_subclass(self): + """ + Test comparison for type ``str`` and its subclass ``StrSubclass``. + """ + + class StrSubclass(str): + + pass + + c1 = StrSubclass("a") + c2 = StrSubclass("b") + c3 = StrSubclass("b") # different instance than c2 + self.assertIsNot(c2, c3, "Testcase error: c2 is c3") + + s1 = str("a") + s2 = str("b") + s3 = str("b") # same instance as s2 + self.assertIs(s2, s3, "Testcase error: s2 is not s3") + + self.assert_total_order(c1, c1, 0) + self.assert_total_order(c1, c2, -1) + self.assert_total_order(c2, c3, 0) + + self.assert_total_order(s1, s1, 0) + self.assert_total_order(s1, s2, -1) + self.assert_total_order(s2, s3, 0) + + self.assert_total_order(c1, s2, -1) + self.assert_total_order(c2, s3, 0) + + self.assert_total_order(s1, c2, -1) + self.assert_total_order(s2, c3, 0) + + + def test_numbers(self): + """ + Test comparison for number types. + """ + + i1 = int(10001) + i2 = int(10002) + i3 = i1 + 1 # same value, different instance than i2 + i4 = i2 - 1 # same value, different instance than i1 + i5 = 42 + self.assertIsNot(i2, i3, "Testcase error: i2 is i3") + self.assertIsNot(i1, i4, "Testcase error: i1 is i4") + + f1 = 1.1 + f2 = 2.1 + f3 = f1 + 1 # same value, different instance than f2 + f4 = f2 - 1 # same value, different instance than f1 + f5 = 42.0 + self.assertIsNot(f2, f3, "Testcase error: f2 is f3") + self.assertIsNot(f1, f4, "Testcase error: f1 is f4") + + c1 = 1+1j + c2 = 2+2j + c3 = 2+2j # same value, different instance than c2 + c4 = 1+1j # same value, different instance than c1 + c5 = 42+0j + self.assertIsNot(c2, c3, "Testcase error: c2 is c3") + self.assertIsNot(c1, c4, "Testcase error: c1 is c4") + + q1 = Fraction(1,2) + q2 = Fraction(2,3) + q3 = Fraction(2,3) # same value, different instance than q2 + q4 = Fraction(1,2) # same value, different instance than q1 + q5 = Fraction(84,2) + self.assertIsNot(q2, q3, "Testcase error: q2 is q3") + self.assertIsNot(q1, q4, "Testcase error: q1 is q4") + + d1 = Decimal('1.2') + d2 = Decimal('2.3') + d3 = Decimal('2.3') # same value, different instance than d2 + d4 = Decimal('1.2') # same value, different instance than d1 + d5 = Decimal('42.0') + self.assertIsNot(d2, d3, "Testcase error: d2 is d3") + self.assertIsNot(d1, d4, "Testcase error: d1 is d4") + + # Same types + + self.assert_total_order(i1, i1, 0) + self.assert_total_order(i1, i2, -1) + self.assert_total_order(i2, i3, 0) + self.assert_total_order(i3, i4, +1) + + self.assert_total_order(f1, f1, 0) + self.assert_total_order(f1, f2, -1) + self.assert_total_order(f2, f3, 0) + self.assert_total_order(f3, f4, +1) + + self.assert_equality_only(c1, c1, True) + self.assert_equality_only(c1, c2, False) + self.assert_equality_only(c2, c3, True) + self.assert_equality_only(c3, c4, False) + + self.assert_total_order(q1, q1, 0) + self.assert_total_order(q1, q2, -1) + self.assert_total_order(q2, q3, 0) + self.assert_total_order(q3, q4, +1) + + self.assert_total_order(d1, d1, 0) + self.assert_total_order(d1, d2, -1) + self.assert_total_order(d2, d3, 0) + self.assert_total_order(d3, d4, +1) + + # Mixing types + + self.assert_total_order(i5, f5, 0) + self.assert_equality_only(i5, c5, True) + self.assert_total_order(i5, q5, 0) + self.assert_total_order(i5, d5, 0) + + self.assert_equality_only(f5, c5, True) + self.assert_total_order(f5, q5, 0) + self.assert_total_order(f5, d5, 0) + + self.assert_equality_only(c5, q5, True) + self.assert_equality_only(c5, d5, True) + + self.assert_total_order(q5, d5, 0) + + self.assert_total_order(i1, f1, +1) + self.assert_equality_only(i1, c1, False) + self.assert_total_order(i1, q1, +1) + self.assert_total_order(i1, d1, +1) + + self.assert_equality_only(f1, c1, False) + self.assert_total_order(f1, q1, +1) + self.assert_total_order(f1, d1, -1) + + self.assert_equality_only(c1, q1, False) + self.assert_equality_only(c1, d1, False) + + self.assert_total_order(q1, d1, -1) + + + def test_sequences(self): + """ + Test comparison for sequences (list, tuple, range). + """ + + l1 = [1,2] + l2 = [2,3] + l3 = [2,3] # same value, different instance than l2 + l4 = [1,2] # same value, different instance than l1 + self.assertIsNot(l2, l3, "Testcase error: l2 is l3") + self.assertIsNot(l1, l4, "Testcase error: l1 is l4") + + t1 = (1,2) + t2 = (2,3) + t3 = (2,3) # same value, different instance than t2 + t4 = (1,2) # same value, different instance than t1 + self.assertIsNot(t2, t3, "Testcase error: t2 is t3") + self.assertIsNot(t1, t4, "Testcase error: t1 is t4") + + r1 = range(1,2) + r2 = range(2,2) + r3 = range(2,2) # same value, different instance than r2 + r4 = range(1,2) # same value, different instance than r1 + self.assertIsNot(r2, r3, "Testcase error: r2 is r3") + self.assertIsNot(r1, r4, "Testcase error: r1 is r4") + + # Same types + + self.assert_total_order(t1, t1, 0) + self.assert_total_order(t1, t2, -1) + self.assert_total_order(t2, t3, 0) + self.assert_total_order(t3, t4, +1) + + self.assert_total_order(l1, l1, 0) + self.assert_total_order(l1, l2, -1) + self.assert_total_order(l2, l3, 0) + self.assert_total_order(l3, l4, +1) + + self.assert_equality_only(r1, r1, True) + self.assert_equality_only(r1, r2, False) + self.assert_equality_only(r2, r3, True) + self.assert_equality_only(r3, r4, False) + + # Mixing types + + self.assert_equality_only(t1, l1, False) + + self.assert_equality_only(l1, r1, False) + + self.assert_equality_only(r1, t1, False) + + + def test_binary_sequences(self): + """ + Test comparison for binary sequences (bytes, bytearray). + """ + + bs1 = b'a1' + bs2 = b'b2' + bs3 = b'b' + b'2' # same value, different instance than bs2 + bs4 = b'a' + b'1' # same value, different instance than bs1 + self.assertIsNot(bs2, bs3, "Testcase error: bs2 is bs3") + self.assertIsNot(bs1, bs4, "Testcase error: bs1 is bs4") + + ba1 = bytearray(b'a1') + ba2 = bytearray(b'b2') + ba3 = bytearray(b'b2') # same value, different instance than ba2 + ba4 = bytearray(b'a1') # same value, different instance than ba1 + self.assertIsNot(ba2, ba3, "Testcase error: ba2 is ba3") + self.assertIsNot(ba1, ba4, "Testcase error: ba1 is ba4") + + # Same types + + self.assert_total_order(bs1, bs1, 0) + self.assert_total_order(bs1, bs2, -1) + self.assert_total_order(bs2, bs3, 0) + self.assert_total_order(bs3, bs4, +1) + + self.assert_total_order(ba1, ba1, 0) + self.assert_total_order(ba1, ba2, -1) + self.assert_total_order(ba2, ba3, 0) + self.assert_total_order(ba3, ba4, +1) + + # Mixing types + + self.assert_total_order(bs1, ba1, 0) + self.assert_total_order(bs1, ba2, -1) + self.assert_total_order(bs2, ba3, 0) + self.assert_total_order(bs3, ba4, +1) + + self.assert_total_order(ba1, bs1, 0) + self.assert_total_order(ba1, bs2, -1) + self.assert_total_order(ba2, bs3, 0) + self.assert_total_order(ba3, bs4, +1) + + + def test_sets(self): + """ + Test comparison for sets (set, frozenset). + """ + + s1 = {1,2} + s2 = {1,2,3} + s3 = {1,2,3} # same value, different instance than s2 + s4 = {1,2} # same value, different instance than s1 + self.assertIsNot(s2, s3, "Testcase error: s2 is s3") + self.assertIsNot(s1, s4, "Testcase error: s1 is s4") + + f1 = frozenset({1,2}) + f2 = frozenset({1,2,3}) + f3 = frozenset({1,2,3}) # same value, different instance than f2 + f4 = frozenset({1,2}) # same value, different instance than f1 + self.assertIsNot(f2, f3, "Testcase error: f2 is f3") + self.assertIsNot(f1, f4, "Testcase error: f1 is f4") + + # Same types + + self.assert_total_order(s1, s1, 0) + self.assert_total_order(s1, s2, -1) + self.assert_total_order(s2, s3, 0) + self.assert_total_order(s3, s4, +1) + + self.assert_total_order(f1, f1, 0) + self.assert_total_order(f1, f2, -1) + self.assert_total_order(f2, f3, 0) + self.assert_total_order(f3, f4, +1) + + # Mixing types + + self.assert_total_order(s1, f1, 0) + self.assert_total_order(s1, f2, -1) + self.assert_total_order(s2, f3, 0) + self.assert_total_order(s3, f4, +1) + + self.assert_total_order(f1, s1, 0) + self.assert_total_order(f1, s2, -1) + self.assert_total_order(f2, s3, 0) + self.assert_total_order(f3, s4, +1) + + + def test_mappings(self): + """ + Test comparison for mappings (dict). + """ + + d1 = {1:"a",2:"b"} + d2 = {2:"b",3:"c"} + d3 = {3:"c",2:"b"} # same value!, different instance than d2 + d4 = {1:"a",2:"b"} # same value, different instance than d1 + self.assertIsNot(d2, d3, "Testcase error: d2 is d3") + self.assertIsNot(d1, d4, "Testcase error: d1 is d4") + + self.assert_equality_only(d1, d1, True) + self.assert_equality_only(d1, d2, False) + self.assert_equality_only(d2, d3, True) + self.assert_equality_only(d3, d4, False) if __name__ == '__main__':