diff -r d686de84dc10 -r 8438ee5eef8b Doc/reference/expressions.rst --- a/Doc/reference/expressions.rst Sun Oct 12 22:58:47 2014 -0400 +++ b/Doc/reference/expressions.rst Mon Oct 13 10:56:12 2014 +0200 @@ -1012,10 +1012,6 @@ .. _comparisons: -.. _is: -.. _is not: -.. _in: -.. _not in: Comparisons =========== @@ -1051,65 +1047,181 @@ *c*, so that, e.g., ``x < y > z`` is perfectly legal (though perhaps not pretty). +.. _comparison-operators: + +Comparison operators +-------------------- + 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. - The 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 - will return ``False``. For example, both ``3 < float('NaN')`` and - ``float('NaN') < 3`` will return ``False``. +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``). -* Bytes objects are compared lexicographically using the numeric values of their - elements. +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. -* 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! +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. -* 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. +The following list describes the comparison behavior of the most important +built-in types. Unless otherwise stated, two instances of built-in types can +only be compared if they are of the same type or one is a subtype of the other; +otherwise, :exc:`TypeError` is raised: - 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]``). +* 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 the same type and between different of those types. Within + the limits of the types involved, they compare mathematically + (algorithmically) correct without loss of precision, and since version 3.2 + also between different types (for example, ``1 == 1.0`` is true). -* Mappings (dictionaries) compare equal if and only if they have the same - ``(key, value)`` pairs. Order comparisons ``('<', '<=', '>=', '>')`` - raise :exc:`TypeError`. + Complex numbers do not support order comparison. + + 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``. -* 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 - 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. +* Binary sequences (instances of :class:`bytes` or :class:`bytearray`) are + compared lexicographically using the numeric values of their elements. -* 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. +* Strings (instances of :class:`str`) are compared lexicographically using the + numerical Unicode code points (the result of the built-in function + :func:`ord`) of their characters. [#]_ -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``. + Strings and binary sequences cannot be directly compared. +* Sequences (instances of :class:`tuple`, :class:`list`, or :class:`range`) are + compared lexicographically using comparison of corresponding elements. + + Ranges do not support order comparison. + + Comparison of sequences enforces reflexivity of its elements. + + 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. + Comparison of non-reflexive elements in collections also tests identity first, + and thus 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:: + + >>> nan = float('NaN') + >>> nan is nan + True + >>> nan == nan + False <-- the defined non-reflexive behavior of NaN + >>> [nan] == [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. + 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 + the same type and between different of those 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). + + Comparison of sets enforces reflexivity of its elements. + +* 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-operators: + +Membership test operators +------------------------- 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 @@ -1152,6 +1264,13 @@ operator: is not pair: identity; test + +.. _is: +.. _is not: + +Identity test operators +----------------------- + 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. [#]_ @@ -1378,12 +1497,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 - ``"\u0327\u0043"`` 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 d686de84dc10 -r 8438ee5eef8b Lib/test/test_compare.py --- a/Lib/test/test_compare.py Sun Oct 12 22:58:47 2014 -0400 +++ b/Lib/test/test_compare.py Mon Oct 13 10:56:12 2014 +0200 @@ -1,28 +1,44 @@ +""" +Test equality and order comparisons. +""" + import unittest +import sys +from fractions import Fraction +from decimal import Decimal + from test import support -class Empty: - def __repr__(self): - return '' -class Cmp: - def __init__(self,arg): - self.arg = arg +# Testcase #1 - def __repr__(self): - return '' % self.arg +class ComparisonTest1(unittest.TestCase): + """ + A testcase that verifies the behavior of equality and order comparisons for + some simple cases. + """ - def __eq__(self, other): - return self.arg == other + class Empty: + def __repr__(self): + return '' -class Anything: - def __eq__(self, other): - return True + class Cmp: + def __init__(self, arg): + self.arg = arg - def __ne__(self, other): - return False + def __repr__(self): + return '' % self.arg -class ComparisonTest(unittest.TestCase): + def __eq__(self, other): + return self.arg == other + + 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 @@ -39,29 +55,770 @@ # Ensure default comparison compares id() of args L = [] for i in range(10): - L.insert(len(L)//2, Empty()) + L.insert(len(L)//2, ComparisonTest1.Empty()) for a in L: for b in L: self.assertEqual(a == b, id(a) == id(b), 'a=%r, b=%r' % (a, b)) def test_ne_defaults_to_not_eq(self): - a = Cmp(1) - b = Cmp(1) + a = ComparisonTest1.Cmp(1) + b = ComparisonTest1.Cmp(1) self.assertTrue(a == b) self.assertFalse(a != b) def test_issue_1393(self): x = lambda: None - self.assertEqual(x, Anything()) - self.assertEqual(Anything(), x) + self.assertEqual(x, ComparisonTest1.Anything()) + self.assertEqual(ComparisonTest1.Anything(), x) y = object() - self.assertEqual(y, Anything()) - self.assertEqual(Anything(), y) + self.assertEqual(y, ComparisonTest1.Anything()) + self.assertEqual(ComparisonTest1.Anything(), y) + + +# Testcase #2 (including some global support functions) + +def _inst_str(obj, value): + """ + Return an str() representation of an instance that has a value. + + This is used in assertion messages. + """ + return "%s(%s) at 0x%08x" % (obj.__class__.__name__, value, id(obj)) + + +class ComparisonTest2(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 without any value-based comparison methods + + class Class_none(object): + + meth = () # names of comparison methods implemented here. See assert_insts(). + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + + # Classes with all combinations of value-based equality comparison methods + + class Class_eq(object): + + meth = ("eq",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __eq__(self, other): + return self.x == other.x + + class Class_ne(object): + + meth = ("ne",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __ne__(self, other): + return self.x != other.x + + class Class_eq_ne(object): + + meth = ("eq", "ne") + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + 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 Class_lt(object): + + meth = ("lt",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __lt__(self, other): + return self.x < other.x + + class Class_gt(object): + + meth = ("gt",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __gt__(self, other): + return self.x > other.x + + class Class_lt_gt(object): + + meth = ("lt", "gt") + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + 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 Class_le(object): + + meth = ("le",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __le__(self, other): + return self.x <= other.x + + class Class_ge(object): + + meth = ("ge",) + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + def __ge__(self, other): + return self.x >= other.x + + class Class_le_ge(object): + + meth = ("le", "ge") + + def __init__(self, x=None): + self.x = x + + def __str__(self): + return _inst_str(self, self.x) + + 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_classes = (Class_none, + Class_eq, Class_ne, Class_eq_ne, # equal group + Class_lt, Class_gt, Class_lt_gt, # l/g than group + Class_le, Class_ge, Class_le_ge) # l/g equal group + + + def is_value_comparable(self, i1, i2, comp_kind): + """ + Return a boolean indicating whether the two input instances can be compared + based on their values (comparison based on identities does not count). + True: The instances can be compared. + False: The instances cannot be compared. + + This is determined based on the type of the two instances. It takes into + account knowledge about the behavior of built-in types. Comparison methods + implemented in user-defined classes are not taken into account. + + Instances of built-in types that are not comparable (e.g. that have default + order comparison), or instances of built-in types that will be compared + based on their identities (e.g. that have default equality comparison), or + instances of user-defined classes (for which it is not known whether they + implement comparison methods) will be returned as False. + + i1, i2: The instances in question. + + comp_kind: Enumeration that indicates the kind of comparison: + "equal": Equality (==, !=) + "order": Ordering (<, <=, >=, >) + """ + + # Numbers are comparable across types. + # Except for complex, which is not orderable. + number_types = (int, float, complex, Fraction, Decimal) + if isinstance(i1, number_types) and isinstance(i2, number_types): + if comp_kind == "order" and \ + (isinstance(i1, complex) or isinstance(i2, complex)): + return False + else: + return True + + # Sets are comparable across types. + set_types = (set, frozenset) + if isinstance(i1, set_types) and isinstance(i2, set_types): + return True + + # Other collections are comparable only within the same type (i.e. both + # instances are of same type, including subtypes). + # Except for range and dict, which are not orderable. + for t in (str, bytes, tuple, list, range, dict): + if isinstance(i1, t) and not isinstance(i2, t): + return False + if isinstance(i2, t) and not isinstance(i1, t): + return False + if isinstance(i1, t) and isinstance(i2, t): + if comp_kind == "order" and \ + (isinstance(i1, (range, dict)) or isinstance(i2, (range, dict))): + return False + else: + return True + + # For all other types, return False (see description) + return False + + + def create_sorted_insts(self, class_, values): + """ + Create a list of instances of class_ and return them in a list. + + The number of instances created is the length of the values list. + + Values is a list of values to be assigned to data attribute "x" of the + instances. + + The created instances are sorted by identity, and the values are assigned + in that order. This allows testcases to control that between two instances, + the order of identity is different from the order of the values, in order + to verify whether the default ordering based on identities is used, or the + class-implemented ordering by value. Note, this is just an improvement in + test quality; the tests do not have a functional dependency on that. + """ + + # 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_insts(self, i1, i2, equal, comp, i1_meth=(), i2_meth=()): + """ + Perform comparison-related assertions for two instances. + + This function implements the knowledge about how comparisons are + supposed to work. + + i1, i2: Instances to be tested (of same or different type). + + equal: Boolean indicating the expected equality comparison + True means: i1 == i2 + False means: i1 != i2 + None means: Equality comparison not supported + + comp: Integer indicating the expected ordering relationship + between the two instances: + <0 means: i1 < i2 + 0 means: i1 == i2 + >0 means: i1 > i2 + None means: Order comparison not supported + + i1_meth, i2_meth: Tuple of method names (without leading and trailing + underscores) that are expected to be implemented for each + instance. Possible values for the tuple items are: + "eq", "ne", "lt", "le", "gt", "ge", "cmp" + """ + + msg = "i1=%s, i2=%s, equal=%s, comp=%s, i1_meth=%s, i2_meth=%s" %\ + (i1, i2, equal, comp, i1_meth, i2_meth) + + # Make sure the Python version is used for which these rules are valid. + self.assertEqual(sys.version_info[0], 3, "Version error: These tests are specific to Python 3") + + # Test == comparison (symmetrical, so we can test both original and swapped variant) + if ("eq" in i1_meth or "eq" in i2_meth or \ + self.is_value_comparable(i1, i2, "equal")): + self.assertIsNotNone(equal, "Testcase error: Unexpected value for equal: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 == i2, equal, msg) + self.assertEqual(i2 == i1, equal, msg) + else: + # There are no value-based comparison methods, expect identity-based comparison + self.assertEqual(i1 == i2, id(i1) == id(i2), msg) + self.assertEqual(i2 == i1, id(i1) == id(i2), msg) + + # Test != comparison, part 1 (is not symmetrical) + if ("ne" in i1_meth or "eq" in i1_meth or "eq" in i2_meth or \ + # __eq__() on either side is used if __ne__() is not implemented. + # __ne__() on other side is not used. + self.is_value_comparable(i1, i2, "equal")): + self.assertIsNotNone(equal, "Testcase error: Unexpected value for equal: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 != i2, not equal, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + self.assertEqual(i1 != i2, id(i1) != id(i2), msg) + + # Test != comparison, part 2 (is not symmetrical) + if ("ne" in i2_meth or "eq" in i1_meth or "eq" in i2_meth or \ + # __eq__() on either side is used if __ne__() is not implemented. + # __ne__() on other side is not used. + self.is_value_comparable(i1, i2, "equal")): + self.assertIsNotNone(equal, "Testcase error: Unexpected value for equal: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i2 != i1, not equal, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + self.assertEqual(i2 != i1, id(i1) != id(i2), msg) + + # Test < comparison (symmetrical) + if ("lt" in i1_meth or "gt" in i2_meth or \ + self.is_value_comparable(i1, i2, "order")): + self.assertIsNotNone(comp, "Testcase error: Unexpected value for comp: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 < i2, comp < 0, msg) + self.assertEqual(i2 > i1, comp < 0, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + with self.assertRaises(TypeError, msg=msg): + i1 < i2 + with self.assertRaises(TypeError, msg=msg): + i2 > i1 + + # Test <= comparison (symmetrical) + if ("le" in i1_meth or "ge" in i2_meth or \ + self.is_value_comparable(i1, i2, "order")): + self.assertIsNotNone(comp, "Testcase error: Unexpected value for comp: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 <= i2, comp <= 0, msg) + self.assertEqual(i2 >= i1, comp <= 0, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + with self.assertRaises(TypeError, msg=msg): + i1 <= i2 + with self.assertRaises(TypeError, msg=msg): + i2 >= i1 + + # Test > comparison (symmetrical) + if ("gt" in i1_meth or "lt" in i2_meth or \ + self.is_value_comparable(i1, i2, "order")): + self.assertIsNotNone(comp, "Testcase error: Unexpected value for comp: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 > i2, comp > 0, msg) + self.assertEqual(i2 < i1, comp > 0, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + with self.assertRaises(TypeError, msg=msg): + i1 > i2 + with self.assertRaises(TypeError, msg=msg): + i2 < i1 + + # Test >= comparison (symmetrical) + if ("ge" in i1_meth or "le" in i2_meth or \ + self.is_value_comparable(i1, i2, "order")): + self.assertIsNotNone(comp, "Testcase error: Unexpected value for comp: None") + # There are value-based comparison methods, expect what the testcase defined + self.assertEqual(i1 >= i2, comp >= 0, msg) + self.assertEqual(i2 <= i1, comp >= 0, msg) + else: + # There are no value-based comparison methods, expect default behavior of object + with self.assertRaises(TypeError, msg=msg): + i1 >= i2 + with self.assertRaises(TypeError, msg=msg): + i2 <= i1 + + + def test_objects(self): + """ + Test comparison for two instances of type 'object'. + """ + + i1 = object() + i2 = object() + + self.assert_insts(i1, i1, True, 0) + self.assert_insts(i1, i2, False, id(i1)-id(i2)) + + + def test_classes(self): + """ + Test comparison for two instances of a number of classes derived from + object, 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. + """ + + for cls_a in ComparisonTest2.all_classes: + + for cls_b in ComparisonTest2.all_classes: + + insts_a = self.create_sorted_insts(cls_a, (1, 2, 2, 1)) + insts_b = self.create_sorted_insts(cls_b, (1, 2, 2, 1)) + + # Because the sorted creation of instance lists is only sorted within a + # class, it is arbitrary how they are ordered across classes. As a + # result, it cannot be predicted whether the values are ascending or + # descending w.r.t. the identity. However, we test both directions, + # so the interesting case of descending is always included (it is + # interesting because the order based on the value is opposite to the + # order based on the identity). + + self.assert_insts(insts_a[0], insts_b[0], True, 0, cls_a.meth, cls_b.meth) # identical (if same class) or same value (otherwise) + self.assert_insts(insts_a[0], insts_b[1], False, -1, cls_a.meth, cls_b.meth) # ascending or descending value + self.assert_insts(insts_a[1], insts_b[2], True, 0, cls_a.meth, cls_b.meth) # same value, different identity (even for same class) + self.assert_insts(insts_a[2], insts_b[3], False, +1, cls_a.meth, cls_b.meth) # descending or ascending value + + self.assert_insts(insts_b[0], insts_a[0], True, 0, cls_b.meth, cls_a.meth) # identical (if same class) or same value (otherwise) + self.assert_insts(insts_b[0], insts_a[1], False, -1, cls_b.meth, cls_a.meth) # ascending or descending value + self.assert_insts(insts_b[1], insts_a[2], True, 0, cls_b.meth, cls_a.meth) # same value, different identity (even for same class) + self.assert_insts(insts_b[2], insts_a[3], False, +1, cls_b.meth, cls_a.meth) # descending or ascending value + + + def test_class_str(self): + """ + Test comparison for string and class Class_str derived from string. + """ + + str_meth = () # str is a built-in type + + class Class_str(str): + + meth = str_meth # inherits all of them from str + + def some_func(self): + return NotImplemented + + c1 = Class_str("a") + c2 = Class_str("b") + c3 = Class_str("b") # different instance as c2 + + s1 = str("a") + s2 = str("b") + s3 = str("b") # same instance as s2 + + self.assert_insts(c1, c1, True, 0, Class_str.meth, Class_str.meth) + self.assert_insts(c1, c2, False, -1, Class_str.meth, Class_str.meth) + self.assert_insts(c2, c3, True, 0, Class_str.meth, Class_str.meth) + + self.assert_insts(s1, s1, True, 0, str_meth, str_meth) + self.assert_insts(s1, s2, False, -1, str_meth, str_meth) + self.assert_insts(s2, s3, True, 0, str_meth, str_meth) + + self.assert_insts(c1, s2, False, -1, Class_str.meth, str_meth) + self.assert_insts(c2, s3, True, 0, Class_str.meth, str_meth) + + self.assert_insts(s1, c2, False, -1, str_meth, Class_str.meth) + self.assert_insts(s2, c3, True, 0, str_meth, Class_str.meth) + + + def test_numbers(self): + """ + Test comparison for number types. + """ + + num_meth = () # all number types are built-in types + + i1 = 10001 # beyond the range of 0..255 where they are shared objects + i2 = 10002 + i3 = 10002 + i4 = 10001 + i5 = 42 + + f1 = 1.1 + f2 = 2.1 + f3 = 2.1 + f4 = 1.1 + f5 = 42.0 + + c1 = 1+1j + c2 = 2+2j + c3 = 2+2j + c4 = 1+1j + c5 = 42+0j + + q1 = Fraction(1,2) + q2 = Fraction(2,3) + q3 = Fraction(2,3) + q4 = Fraction(1,2) + q5 = Fraction(84,2) + + d1 = Decimal(1.2) + d2 = Decimal(2.3) + d3 = Decimal(2.3) + d4 = Decimal(1.2) + d5 = Decimal(42.0) + + # Same types + + self.assert_insts(i1, i1, True, 0, num_meth, num_meth) + self.assert_insts(i1, i2, False, -1, num_meth, num_meth) + self.assert_insts(i2, i3, True, 0, num_meth, num_meth) + self.assert_insts(i3, i4, False, +1, num_meth, num_meth) + + self.assert_insts(f1, f1, True, 0, num_meth, num_meth) + self.assert_insts(f1, f2, False, -1, num_meth, num_meth) + self.assert_insts(f2, f3, True, 0, num_meth, num_meth) + self.assert_insts(f3, f4, False, +1, num_meth, num_meth) + + self.assert_insts(c1, c1, True, None, num_meth, num_meth) + self.assert_insts(c1, c2, False, None, num_meth, num_meth) + self.assert_insts(c2, c3, True, None, num_meth, num_meth) + self.assert_insts(c3, c4, False, None, num_meth, num_meth) + + self.assert_insts(q1, q1, True, 0, num_meth, num_meth) + self.assert_insts(q1, q2, False, -1, num_meth, num_meth) + self.assert_insts(q2, q3, True, 0, num_meth, num_meth) + self.assert_insts(q3, q4, False, +1, num_meth, num_meth) + + self.assert_insts(d1, d1, True, 0, num_meth, num_meth) + self.assert_insts(d1, d2, False, -1, num_meth, num_meth) + self.assert_insts(d2, d3, True, 0, num_meth, num_meth) + self.assert_insts(d3, d4, False, +1, num_meth, num_meth) + + # Mixing types + + self.assert_insts(i5, f5, True, 0, num_meth, num_meth) + self.assert_insts(i5, c5, True, 0, num_meth, num_meth) + self.assert_insts(i5, q5, True, 0, num_meth, num_meth) + self.assert_insts(i5, d5, True, 0, num_meth, num_meth) + + self.assert_insts(f5, c5, True, 0, num_meth, num_meth) + self.assert_insts(f5, q5, True, 0, num_meth, num_meth) + self.assert_insts(f5, d5, True, 0, num_meth, num_meth) + + self.assert_insts(c5, q5, True, 0, num_meth, num_meth) + self.assert_insts(c5, d5, True, 0, num_meth, num_meth) + + self.assert_insts(q5, d5, True, 0, num_meth, num_meth) + + self.assert_insts(i1, f1, False, +1, num_meth, num_meth) + self.assert_insts(i1, c1, False, +1, num_meth, num_meth) + self.assert_insts(i1, q1, False, +1, num_meth, num_meth) + self.assert_insts(i1, d1, False, +1, num_meth, num_meth) + + self.assert_insts(f1, c1, False, -1, num_meth, num_meth) + self.assert_insts(f1, q1, False, +1, num_meth, num_meth) + self.assert_insts(f1, d1, False, -1, num_meth, num_meth) + + self.assert_insts(c1, q1, False, -1, num_meth, num_meth) + self.assert_insts(c1, d1, False, -1, num_meth, num_meth) + + self.assert_insts(q1, d1, False, -1, num_meth, num_meth) + + + def test_sequences(self): + """ + Test comparison for sequences (list, tuple, range). + """ + + seq_meth = () # these sequences are built-in types + + l1 = [1,2] + l2 = [2,3] + l3 = [2,3] + l4 = [1,2] + + t1 = (1,2) + t2 = (2,3) + t3 = (2,3) + t4 = (1,2) + + r1 = range(1,2) + r2 = range(2,2) + r3 = range(2,2) + r4 = range(1,2) + + # Same types + + self.assert_insts(t1, t1, True, 0, seq_meth, seq_meth) + self.assert_insts(t1, t2, False, -1, seq_meth, seq_meth) + self.assert_insts(t2, t3, True, 0, seq_meth, seq_meth) + self.assert_insts(t3, t4, False, +1, seq_meth, seq_meth) + + self.assert_insts(l1, l1, True, 0, seq_meth, seq_meth) + self.assert_insts(l1, l2, False, -1, seq_meth, seq_meth) + self.assert_insts(l2, l3, True, 0, seq_meth, seq_meth) + self.assert_insts(l3, l4, False, +1, seq_meth, seq_meth) + + self.assert_insts(r1, r1, True, None, seq_meth, seq_meth) + self.assert_insts(r1, r2, False, None, seq_meth, seq_meth) + self.assert_insts(r2, r3, True, None, seq_meth, seq_meth) + self.assert_insts(r3, r4, False, None, seq_meth, seq_meth) + + # Mixing types + + self.assert_insts(t1, l1, False, None, seq_meth, seq_meth) + + self.assert_insts(l1, r1, False, None, seq_meth, seq_meth) + + self.assert_insts(r1, t1, False, None, seq_meth, seq_meth) + + + def test_sets(self): + """ + Test comparison for sets (set, frozenset). + """ + + set_meth = () # these sets are built-in types + + s1 = {1,2} + s2 = {1,2,3} # for sets, <, <=, >=, > are subset-of relations + s3 = {1,2,3} + s4 = {1,2} + + f1 = frozenset({1,2}) + f2 = frozenset({1,2,3}) + f3 = frozenset({1,2,3}) + f4 = frozenset({1,2}) + + # Same types + + self.assert_insts(s1, s1, True, 0, set_meth, set_meth) + self.assert_insts(s1, s2, False, -1, set_meth, set_meth) + self.assert_insts(s2, s3, True, 0, set_meth, set_meth) + self.assert_insts(s3, s4, False, +1, set_meth, set_meth) + + self.assert_insts(f1, f1, True, 0, set_meth, set_meth) + self.assert_insts(f1, f2, False, -1, set_meth, set_meth) + self.assert_insts(f2, f3, True, 0, set_meth, set_meth) + self.assert_insts(f3, f4, False, +1, set_meth, set_meth) + + # Mixing types + + self.assert_insts(s1, f1, True, 0, set_meth, set_meth) + self.assert_insts(s1, f2, False, -1, set_meth, set_meth) + self.assert_insts(s2, f3, True, 0, set_meth, set_meth) + self.assert_insts(s3, f4, False, +1, set_meth, set_meth) + + self.assert_insts(f1, s1, True, 0, set_meth, set_meth) + self.assert_insts(f1, s2, False, -1, set_meth, set_meth) + self.assert_insts(f2, s3, True, 0, set_meth, set_meth) + self.assert_insts(f3, s4, False, +1, set_meth, set_meth) + + + def test_mappings(self): + """ + Test comparison for mappings (dict). + """ + + map_meth = () # these mappings are built-in types + + d1 = {1:"a",2:"b"} + d2 = {2:"b",3:"c"} + d3 = {3:"c",2:"b"} # same value as d2 + d4 = {1:"a",2:"b"} + + self.assert_insts(d1, d1, True, None, map_meth, map_meth) + self.assert_insts(d1, d2, False, None, map_meth, map_meth) + self.assert_insts(d2, d3, True, None, map_meth, map_meth) + self.assert_insts(d3, d4, False, None, map_meth, map_meth) + + + def test__internal_is_value_comparable(self): + """ + Internal test for is_value_comparable(). + """ + + tests = ( + # tuple( inst1, inst2, comp_kind, exp_result ) + + # Numbers + (1, 1.0, "equal", True), + (1.0, 1+1j, "equal", True), + (1.0, 1+1j, "order", False), + (1+1j, Fraction(2,3), "equal", True), + (1+1j, Decimal(1234.56), "equal", True), + # with others + (1, bytes(b"b"), "equal", False), + + # String and bytes + (str("a"), str("b"), "equal", True), + (bytes(b"a"), bytes(b"b"), "equal", True), + # with others + (str("a"), bytes(b"b"), "equal", False), + + # Sequences (list, tuple, range) + ([1,2], [2,3], "equal", True), + ((1,2), (2,3), "equal", True), + (range(1,2), range(2,2), "equal", True), + (range(1,2), range(2,2), "order", False), + # with others + ([1,2], (2,3), "equal", False), + ((1,2), range(2,2), "equal", False), + ((1,2), range(2,2), "order", False), + + # Sets (set, frozenset) + ({1,2}, {2,3}, "equal", True), + ({1,2}, {2,3}, "order", True), + (frozenset({1,2}), frozenset({2,3}), "equal", True), + (frozenset({1,2}), frozenset({2,3}), "order", True), + ({1,2}, frozenset({2,3}), "equal", True), + ({1,2}, frozenset({2,3}), "order", True), + (frozenset({1,2}), {2,3}, "equal", True), + (frozenset({1,2}), {2,3}, "order", True), + # with others + ({1,2}, [2,3], "equal", False), + ({1,2}, (2,3), "order", False), + + # Mappings (dicts) + ({1:"a",2:"b"}, {2:"b",3:"c"}, "equal", True), + # with others + ({1:"a",2:"b"}, {2,3}, "equal", False), + ) + + for inst1, inst2, comp_kind, exp_result in tests: + act_result = self.is_value_comparable(inst1, inst2, comp_kind) + self.assertEqual(act_result, exp_result, + "inst1=%s, inst2=%s, comp_kind=%s, exp_result=%s, act_result=%s" %\ + (inst1, inst2, comp_kind, exp_result, act_result)) def test_main(): - support.run_unittest(ComparisonTest) + support.run_unittest(ComparisonTest1) + support.run_unittest(ComparisonTest2) if __name__ == '__main__': test_main()