diff -r a824c40e8fc0 -r d69c5520c617 Doc/reference/expressions.rst
--- a/Doc/reference/expressions.rst Sat Feb 21 15:26:02 2015 -0800
+++ b/Doc/reference/expressions.rst Mon Mar 02 19:02:13 2015 +0100
@@ -1021,10 +1021,6 @@
.. _comparisons:
-.. _is:
-.. _is not:
-.. _in:
-.. _not in:
Comparisons
===========
@@ -1060,66 +1056,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
@@ -1161,6 +1273,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. [#]_
@@ -1388,12 +1507,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 a824c40e8fc0 -r d69c5520c617 Lib/test/test_compare.py
--- a/Lib/test/test_compare.py Sat Feb 21 15:26:02 2015 -0800
+++ b/Lib/test/test_compare.py Mon Mar 02 19:02:13 2015 +0100
@@ -1,28 +1,41 @@
+"""
+Test equality and order comparisons.
+"""
+
import unittest
+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
+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
@@ -39,16 +52,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, ComparisonSimpleTest.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 = ComparisonSimpleTest.Cmp(1)
+ b = ComparisonSimpleTest.Cmp(1)
+ c = ComparisonSimpleTest.Cmp(2)
self.assertIs(a == b, True)
self.assertIs(a != b, False)
self.assertIs(a != c, True)
@@ -114,15 +126,854 @@
def test_issue_1393(self):
x = lambda: None
- self.assertEqual(x, Anything())
- self.assertEqual(Anything(), x)
+ self.assertEqual(x, ComparisonSimpleTest.Anything())
+ self.assertEqual(ComparisonSimpleTest.Anything(), x)
y = object()
- self.assertEqual(y, Anything())
- self.assertEqual(Anything(), y)
+ self.assertEqual(y, ComparisonSimpleTest.Anything())
+ self.assertEqual(ComparisonSimpleTest.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.
+ """
+ meth = () # names of comparison methods implemented here. See
+ # assert_comparisons().
+ def __init__(self, x=None):
+ self.x = x
+
+
+ # 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 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 order-comparable (e.g. that
+ have default order comparison), or instances of built-in types that
+ will be equality-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 order-comparable.
+ 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 their types.
+ # Sets are not totally ordered, but the order comparison operations
+ # are supported (they test for the subset/superset relationship).
+ set_types = (set, frozenset)
+ if isinstance(i1, set_types) and isinstance(i2, set_types):
+ return True
+
+ # Byte sequences are comparable across their types.
+ bseq_types = (bytes, bytearray)
+ if isinstance(i1, bseq_types) and isinstance(i2, bseq_types):
+ return True
+
+ # Other built-in 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, tuple, list, range, dict):
+ isa1 = isinstance(i1, t)
+ isa2 = isinstance(i2, t)
+ if isa1 and isa2:
+ if comp_kind == "order" and t in (range, dict):
+ return False
+ else:
+ return True
+ if isa1 or isa2:
+ return False
+
+ # For all other combinations of types, return False (see description)
+ return False
+
+
+ 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_comparisons(self, i1, i2, equal, comp, i1_meth=None,
+ i2_meth=None):
+ """
+ Perform assertions on equality and ordering comparison of two
+ instances.
+
+ This function implements the knowledge about how equality and ordering
+ comparisons are supposed to work.
+
+ i1, i2: Instances to be tested (of same or different type).
+
+ equal: Boolean indicating the expected equality comparison result:
+ True means: i1 == i2
+ False means: i1 != i2
+
+ comp: Integer indicating the expected order comparison result:
+ <0 means: i1 < i2
+ 0 means: i1 == i2
+ >0 means: i1 > i2
+ None means: Undefined result
+
+ Note that the expected results shown above just indicate
+ which order comparison expression evaluates to True; they
+ do not make any statement about whether the ordering is a
+ total ordering.
+
+ i1_meth, i2_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".
+ None means the same as an empty tuple.
+ """
+
+ if i1_meth is None:
+ i1_meth = ()
+ if i2_meth is None:
+ i2_meth = ()
+
+ args_equal = dict(i1=i1, i2=i2, equal=equal, i1_meth=i1_meth,
+ i2_meth=i2_meth)
+ with self.subTest(**args_equal):
+ self.assert_eq_comparison_subtest(**args_equal)
+ with self.subTest(**args_equal):
+ self.assert_ne_comparison_subtest(**args_equal)
+
+ args_comp = dict(i1=i1, i2=i2, comp=comp, i1_meth=i1_meth,
+ i2_meth=i2_meth)
+ with self.subTest(**args_comp):
+ self.assert_lt_comparison_subtest(**args_comp)
+ with self.subTest(**args_comp):
+ self.assert_le_comparison_subtest(**args_comp)
+ with self.subTest(**args_comp):
+ self.assert_gt_comparison_subtest(**args_comp)
+ with self.subTest(**args_comp):
+ self.assert_ge_comparison_subtest(**args_comp)
+
+ def assert_eq_comparison_subtest(self, *, i1, i2, equal, i1_meth, i2_meth):
+ """ Test "==" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ self.assertIsNotNone(equal,
+ "Testcase error: Unexpected value for equal: None")
+
+ if "eq" in i1_meth \
+ or "eq" in i2_meth \
+ or self.is_value_comparable(i1, i2, "equal"):
+ # There are value-based comparison methods;
+ # we expect what the testcase defined
+ self.assertEqual(i1 == i2, equal)
+ self.assertEqual(i2 == i1, equal)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ self.assertEqual(i1 == i2, i1 is i2)
+ self.assertEqual(i2 == i1, i2 is i1)
+
+ def assert_ne_comparison_subtest(self, *, i1, i2, equal, i1_meth, i2_meth):
+ """ Test "!=" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ self.assertIsNotNone(equal,
+ "Testcase error: Unexpected value for equal: None")
+
+ if not {"ne", "eq"}.isdisjoint(i1_meth + i2_meth) \
+ or self.is_value_comparable(i1, i2, "equal"):
+ # There are value-based comparison methods;
+ # we expect what the testcase defined
+ self.assertEqual(i1 != i2, not equal)
+ self.assertEqual(i2 != i1, not equal)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ self.assertEqual(i1 != i2, i1 is not i2)
+ self.assertEqual(i2 != i1, i1 is not i2)
+
+ def assert_lt_comparison_subtest(self, *, i1, i2, comp, i1_meth, i2_meth):
+ """ Test "<" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ 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;
+ # we expect what the testcase defined
+ self.assertEqual(i1 < i2, comp < 0)
+ self.assertEqual(i2 > i1, comp < 0)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ with self.assertRaises(TypeError):
+ i1 < i2
+ with self.assertRaises(TypeError):
+ i2 > i1
+
+ def assert_le_comparison_subtest(self, *, i1, i2, comp, i1_meth, i2_meth):
+ """ Test "<=" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ 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;
+ # we expect what the testcase defined
+ self.assertEqual(i1 <= i2, comp <= 0)
+ self.assertEqual(i2 >= i1, comp <= 0)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ with self.assertRaises(TypeError):
+ i1 <= i2
+ with self.assertRaises(TypeError):
+ i2 >= i1
+
+ def assert_gt_comparison_subtest(self, *, i1, i2, comp, i1_meth, i2_meth):
+ """ Test ">" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ 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;
+ # we expect what the testcase defined
+ self.assertEqual(i1 > i2, comp > 0)
+ self.assertEqual(i2 < i1, comp > 0)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ with self.assertRaises(TypeError):
+ i1 > i2
+ with self.assertRaises(TypeError):
+ i2 < i1
+
+ def assert_ge_comparison_subtest(self, *, i1, i2, comp, i1_meth, i2_meth):
+ """ Test ">=" comparison.
+ The comparison is performed in both directions of the operands.
+ """
+
+ 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;
+ # we expect what the testcase defined
+ self.assertEqual(i1 >= i2, comp >= 0)
+ self.assertEqual(i2 <= i1, comp >= 0)
+ else:
+ # There are no value-based comparison methods;
+ # we expect the default behavior of object
+ with self.assertRaises(TypeError):
+ i1 >= i2
+ with self.assertRaises(TypeError):
+ i2 <= i1
+
+
+ def test_objects(self):
+ """
+ Test comparison for two instances of type 'object'.
+ """
+
+ i1 = object()
+ i2 = object()
+
+ self.assert_comparisons(i1, i1, True, None)
+ self.assert_comparisons(i1, i2, False, None)
+
+
+ def test_comp_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.
+ """
+
+ # Comparisons of objects of the same class.
+
+ for cls in ComparisonFullTest.all_comp_classes:
+
+ insts = self.create_sorted_insts(cls, (1, 2, 1))
+
+ # same object
+ self.assert_comparisons(insts[0], insts[0], True, 0,
+ cls.meth, cls.meth)
+
+ # different objects, same value
+ self.assert_comparisons(insts[0], insts[2], True, 0,
+ cls.meth, cls.meth)
+
+ # different objects, value ascending for ascending identities
+ self.assert_comparisons(insts[0], insts[1], False, -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_comparisons(insts[1], insts[2], False, +1,
+ cls.meth, cls.meth)
+
+ # Comparisons of objects of the combination of all classes.
+
+ for cls_a in ComparisonFullTest.all_comp_classes:
+
+ for cls_b in ComparisonFullTest.all_comp_classes:
+
+ insts_a = self.create_sorted_insts(cls_a, (1, 2))
+ insts_b = self.create_sorted_insts(cls_b, (1, 2))
+
+ # Because we are comparing always across the lists, and the
+ # object identities are ascending only within each list, it
+ # cannot be predicted whether the values of the compared
+ # objects are ascending or descending for ascending identities.
+ # So basically, the create_sorted_insts() function is used
+ # only as a convenience, but its sorting behavior is not
+ # exploited in this testcase.
+
+ # different objects, same value
+ self.assert_comparisons(insts_a[0], insts_b[0], True, 0,
+ cls_a.meth, cls_b.meth)
+
+ # different objects, ascending or descending value for
+ # ascending identities
+ self.assert_comparisons(insts_a[0], insts_b[1], False, -1,
+ cls_a.meth, cls_b.meth)
+
+
+ def test_str_subclass(self):
+ """
+ Test comparison for type ``str`` and its subclass ``StrSubclass``.
+ """
+
+ str_meth = () # str is a built-in type. Note, we only record
+ # user-defined comparison methods, not those provided by
+ # built-in types.
+
+ class StrSubclass(str):
+
+ meth = str_meth # inherits all of them from str, no additional
+ # user-defined comparison methods.
+
+ c1 = StrSubclass("a")
+ c2 = StrSubclass("b")
+ c3 = StrSubclass("b") # different instance than c2
+ self.assertTrue(c2 is not c3, "Testcase error: c2 is c3")
+
+ s1 = str("a")
+ s2 = str("b")
+ s3 = str("b") # same instance as s2
+ self.assertTrue(s2 is s3, "Testcase error: s2 is not s3")
+
+ self.assert_comparisons(c1, c1, True, 0, StrSubclass.meth,
+ StrSubclass.meth)
+ self.assert_comparisons(c1, c2, False, -1, StrSubclass.meth,
+ StrSubclass.meth)
+ self.assert_comparisons(c2, c3, True, 0, StrSubclass.meth,
+ StrSubclass.meth)
+
+ self.assert_comparisons(s1, s1, True, 0, str_meth, str_meth)
+ self.assert_comparisons(s1, s2, False, -1, str_meth, str_meth)
+ self.assert_comparisons(s2, s3, True, 0, str_meth, str_meth)
+
+ self.assert_comparisons(c1, s2, False, -1, StrSubclass.meth, str_meth)
+ self.assert_comparisons(c2, s3, True, 0, StrSubclass.meth, str_meth)
+
+ self.assert_comparisons(s1, c2, False, -1, str_meth, StrSubclass.meth)
+ self.assert_comparisons(s2, c3, True, 0, str_meth, StrSubclass.meth)
+
+
+ def test_numbers(self):
+ """
+ Test comparison for number types.
+ """
+
+ num_meth = () # all number types are built-in 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.assertTrue(i2 is not i3, "Testcase error: i2 is i3")
+ self.assertTrue(i1 is not 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.assertTrue(f2 is not f3, "Testcase error: f2 is f3")
+ self.assertTrue(f1 is not 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.assertTrue(c2 is not c3, "Testcase error: c2 is c3")
+ self.assertTrue(c1 is not 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.assertTrue(q2 is not q3, "Testcase error: q2 is q3")
+ self.assertTrue(q1 is not 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.assertTrue(d2 is not d3, "Testcase error: d2 is d3")
+ self.assertTrue(d1 is not d4, "Testcase error: d1 is d4")
+
+ # Same types
+
+ self.assert_comparisons(i1, i1, True, 0, num_meth, num_meth)
+ self.assert_comparisons(i1, i2, False, -1, num_meth, num_meth)
+ self.assert_comparisons(i2, i3, True, 0, num_meth, num_meth)
+ self.assert_comparisons(i3, i4, False, +1, num_meth, num_meth)
+
+ self.assert_comparisons(f1, f1, True, 0, num_meth, num_meth)
+ self.assert_comparisons(f1, f2, False, -1, num_meth, num_meth)
+ self.assert_comparisons(f2, f3, True, 0, num_meth, num_meth)
+ self.assert_comparisons(f3, f4, False, +1, num_meth, num_meth)
+
+ self.assert_comparisons(c1, c1, True, None, num_meth, num_meth)
+ self.assert_comparisons(c1, c2, False, None, num_meth, num_meth)
+ self.assert_comparisons(c2, c3, True, None, num_meth, num_meth)
+ self.assert_comparisons(c3, c4, False, None, num_meth, num_meth)
+
+ self.assert_comparisons(q1, q1, True, 0, num_meth, num_meth)
+ self.assert_comparisons(q1, q2, False, -1, num_meth, num_meth)
+ self.assert_comparisons(q2, q3, True, 0, num_meth, num_meth)
+ self.assert_comparisons(q3, q4, False, +1, num_meth, num_meth)
+
+ self.assert_comparisons(d1, d1, True, 0, num_meth, num_meth)
+ self.assert_comparisons(d1, d2, False, -1, num_meth, num_meth)
+ self.assert_comparisons(d2, d3, True, 0, num_meth, num_meth)
+ self.assert_comparisons(d3, d4, False, +1, num_meth, num_meth)
+
+ # Mixing types
+
+ self.assert_comparisons(i5, f5, True, 0, num_meth, num_meth)
+ self.assert_comparisons(i5, c5, True, None, num_meth, num_meth)
+ self.assert_comparisons(i5, q5, True, 0, num_meth, num_meth)
+ self.assert_comparisons(i5, d5, True, 0, num_meth, num_meth)
+
+ self.assert_comparisons(f5, c5, True, None, num_meth, num_meth)
+ self.assert_comparisons(f5, q5, True, 0, num_meth, num_meth)
+ self.assert_comparisons(f5, d5, True, 0, num_meth, num_meth)
+
+ self.assert_comparisons(c5, q5, True, None, num_meth, num_meth)
+ self.assert_comparisons(c5, d5, True, None, num_meth, num_meth)
+
+ self.assert_comparisons(q5, d5, True, 0, num_meth, num_meth)
+
+ self.assert_comparisons(i1, f1, False, +1, num_meth, num_meth)
+ self.assert_comparisons(i1, c1, False, None, num_meth, num_meth)
+ self.assert_comparisons(i1, q1, False, +1, num_meth, num_meth)
+ self.assert_comparisons(i1, d1, False, +1, num_meth, num_meth)
+
+ self.assert_comparisons(f1, c1, False, None, num_meth, num_meth)
+ self.assert_comparisons(f1, q1, False, +1, num_meth, num_meth)
+ self.assert_comparisons(f1, d1, False, -1, num_meth, num_meth)
+
+ self.assert_comparisons(c1, q1, False, None, num_meth, num_meth)
+ self.assert_comparisons(c1, d1, False, None, num_meth, num_meth)
+
+ self.assert_comparisons(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] # same value, different instance than l2
+ l4 = [1,2] # same value, different instance than l1
+ self.assertTrue(l2 is not l3, "Testcase error: l2 is l3")
+ self.assertTrue(l1 is not 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.assertTrue(t2 is not t3, "Testcase error: t2 is t3")
+ self.assertTrue(t1 is not 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.assertTrue(r2 is not r3, "Testcase error: r2 is r3")
+ self.assertTrue(r1 is not r4, "Testcase error: r1 is r4")
+
+ # Same types
+
+ self.assert_comparisons(t1, t1, True, 0, seq_meth, seq_meth)
+ self.assert_comparisons(t1, t2, False, -1, seq_meth, seq_meth)
+ self.assert_comparisons(t2, t3, True, 0, seq_meth, seq_meth)
+ self.assert_comparisons(t3, t4, False, +1, seq_meth, seq_meth)
+
+ self.assert_comparisons(l1, l1, True, 0, seq_meth, seq_meth)
+ self.assert_comparisons(l1, l2, False, -1, seq_meth, seq_meth)
+ self.assert_comparisons(l2, l3, True, 0, seq_meth, seq_meth)
+ self.assert_comparisons(l3, l4, False, +1, seq_meth, seq_meth)
+
+ self.assert_comparisons(r1, r1, True, None, seq_meth, seq_meth)
+ self.assert_comparisons(r1, r2, False, None, seq_meth, seq_meth)
+ self.assert_comparisons(r2, r3, True, None, seq_meth, seq_meth)
+ self.assert_comparisons(r3, r4, False, None, seq_meth, seq_meth)
+
+ # Mixing types
+
+ self.assert_comparisons(t1, l1, False, None, seq_meth, seq_meth)
+
+ self.assert_comparisons(l1, r1, False, None, seq_meth, seq_meth)
+
+ self.assert_comparisons(r1, t1, False, None, seq_meth, seq_meth)
+
+
+ def test_binary_sequences(self):
+ """
+ Test comparison for binary sequences (bytes, bytearray).
+ """
+
+ bseq_meth = () # these are built-in types
+
+ 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.assertTrue(bs2 is not bs3, "Testcase error: bs2 is bs3")
+ self.assertTrue(bs1 is not 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.assertTrue(ba2 is not ba3, "Testcase error: ba2 is ba3")
+ self.assertTrue(ba1 is not ba4, "Testcase error: ba1 is ba4")
+
+ # Same types
+
+ self.assert_comparisons(bs1, bs1, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs1, bs2, False, -1, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs2, bs3, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs3, bs4, False, +1, bseq_meth, bseq_meth)
+
+ self.assert_comparisons(ba1, ba1, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba1, ba2, False, -1, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba2, ba3, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba3, ba4, False, +1, bseq_meth, bseq_meth)
+
+ # Mixing types
+
+ self.assert_comparisons(bs1, ba1, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs1, ba2, False, -1, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs2, ba3, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(bs3, ba4, False, +1, bseq_meth, bseq_meth)
+
+ self.assert_comparisons(ba1, bs1, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba1, bs2, False, -1, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba2, bs3, True, 0, bseq_meth, bseq_meth)
+ self.assert_comparisons(ba3, bs4, False, +1, bseq_meth, bseq_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} # same value, different instance than s2
+ s4 = {1,2} # same value, different instance than s1
+ self.assertTrue(s2 is not s3, "Testcase error: s2 is s3")
+ self.assertTrue(s1 is not 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.assertTrue(f2 is not f3, "Testcase error: f2 is f3")
+ self.assertTrue(f1 is not f4, "Testcase error: f1 is f4")
+
+ # Same types
+
+ self.assert_comparisons(s1, s1, True, 0, set_meth, set_meth)
+ self.assert_comparisons(s1, s2, False, -1, set_meth, set_meth)
+ self.assert_comparisons(s2, s3, True, 0, set_meth, set_meth)
+ self.assert_comparisons(s3, s4, False, +1, set_meth, set_meth)
+
+ self.assert_comparisons(f1, f1, True, 0, set_meth, set_meth)
+ self.assert_comparisons(f1, f2, False, -1, set_meth, set_meth)
+ self.assert_comparisons(f2, f3, True, 0, set_meth, set_meth)
+ self.assert_comparisons(f3, f4, False, +1, set_meth, set_meth)
+
+ # Mixing types
+
+ self.assert_comparisons(s1, f1, True, 0, set_meth, set_meth)
+ self.assert_comparisons(s1, f2, False, -1, set_meth, set_meth)
+ self.assert_comparisons(s2, f3, True, 0, set_meth, set_meth)
+ self.assert_comparisons(s3, f4, False, +1, set_meth, set_meth)
+
+ self.assert_comparisons(f1, s1, True, 0, set_meth, set_meth)
+ self.assert_comparisons(f1, s2, False, -1, set_meth, set_meth)
+ self.assert_comparisons(f2, s3, True, 0, set_meth, set_meth)
+ self.assert_comparisons(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!, different instance than d2
+ d4 = {1:"a",2:"b"} # same value, different instance than d1
+ self.assertTrue(d2 is not d3, "Testcase error: d2 is d3")
+ self.assertTrue(d1 is not d4, "Testcase error: d1 is d4")
+
+ self.assert_comparisons(d1, d1, True, None, map_meth, map_meth)
+ self.assert_comparisons(d1, d2, False, None, map_meth, map_meth)
+ self.assert_comparisons(d2, d3, True, None, map_meth, map_meth)
+ self.assert_comparisons(d3, d4, False, None, map_meth, map_meth)
+
+
+ def test__is_value_comparable(self):
+ """
+ Internal test for is_value_comparable().
+
+ The extra underscore ensures that this test is executed before
+ the others that use that function.
+ """
+
+ 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 binary sequences (bytes, bytearray)
+ (str("a"), str("b"), "equal", True),
+ (bytes(b"a"), bytes(b"b"), "equal", True),
+ (bytearray(b"a"), bytearray(b"b"), "equal", True),
+ # with others
+ (str("a"), bytes(b"b"), "equal", False),
+ (str("a"), bytearray(b"b"), "equal", False),
+ (bytes(b"a"), bytearray(b"b"), "equal", True),
+
+ # 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),
+ ([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),
+ ({1:"a",2:"b"}, {2:"b",3:"c"}, "order", False),
+ # 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=%r, inst2=%r, 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(ComparisonSimpleTest)
+ support.run_unittest(ComparisonFullTest)
if __name__ == '__main__':
test_main()