diff options
author | Tim Peters <tim.peters@gmail.com> | 2003-03-02 00:19:49 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2003-03-02 00:19:49 (GMT) |
commit | 44f14b039949005ecc93fd8294933c84fab6f374 (patch) | |
tree | e68c8735baa42a015f34cd37c2d3c89020f8ee97 /Lib | |
parent | 3ba491e6b1747707374e56fd9f0fb958b2aafcd5 (diff) | |
download | cpython-44f14b039949005ecc93fd8294933c84fab6f374.zip cpython-44f14b039949005ecc93fd8294933c84fab6f374.tar.gz cpython-44f14b039949005ecc93fd8294933c84fab6f374.tar.bz2 |
SF bug 693121: Set == non-Set is a TypeError.
Allow mixed-type __eq__ and __ne__ for Set objects. This is messier than
I'd like because Set *also* implements __cmp__. I know of one glitch now:
cmp(s, t) returns 0 now when s and t are both Sets and s == t, despite
that Set.__cmp__ unconditionally raises TypeError (and by intent). The
rub is that __eq__ gets tried first, and the x.__eq__(y) True result
convinces Python that cmp(x, y) is 0 without even calling Set.__cmp__.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/sets.py | 32 | ||||
-rw-r--r-- | Lib/test/test_sets.py | 35 |
2 files changed, 49 insertions, 18 deletions
diff --git a/Lib/sets.py b/Lib/sets.py index 0824fb1..e6a509f 100644 --- a/Lib/sets.py +++ b/Lib/sets.py @@ -102,20 +102,40 @@ class BaseSet(object): """ return self._data.iterkeys() - # Three-way comparison is not supported + # Three-way comparison is not supported. However, because __eq__ is + # tried before __cmp__, if Set x == Set y, x.__eq__(y) returns True and + # then cmp(x, y) returns 0 (Python doesn't actually call __cmp__ in this + # case). def __cmp__(self, other): raise TypeError, "can't compare sets using cmp()" - # Equality comparisons using the underlying dicts + # Equality comparisons using the underlying dicts. Mixed-type comparisons + # are allowed here, where Set == z for non-Set z always returns False, + # and Set != z always True. This allows expressions like "x in y" to + # give the expected result when y is a sequence of mixed types, not + # raising a pointless TypeError just because y contains a Set, or x is + # a Set and y contain's a non-set ("in" invokes only __eq__). + # Subtle: it would be nicer if __eq__ and __ne__ could return + # NotImplemented instead of True or False. Then the other comparand + # would get a chance to determine the result, and if the other comparand + # also returned NotImplemented then it would fall back to object address + # comparison (which would always return False for __eq__ and always + # True for __ne__). However, that doesn't work, because this type + # *also* implements __cmp__: if, e.g., __eq__ returns NotImplemented, + # Python tries __cmp__ next, and the __cmp__ here then raises TypeError. def __eq__(self, other): - self._binary_sanity_check(other) - return self._data == other._data + if isinstance(other, BaseSet): + return self._data == other._data + else: + return False def __ne__(self, other): - self._binary_sanity_check(other) - return self._data != other._data + if isinstance(other, BaseSet): + return self._data != other._data + else: + return True # Copying operations diff --git a/Lib/test/test_sets.py b/Lib/test/test_sets.py index 9223596..d8b7f3f 100644 --- a/Lib/test/test_sets.py +++ b/Lib/test/test_sets.py @@ -232,7 +232,16 @@ class TestBinaryOps(unittest.TestCase): def test_cmp(self): a, b = Set('a'), Set('b') - self.assertRaises(TypeError, cmp, (a,b)) + self.assertRaises(TypeError, cmp, a, b) + + # You can view this as a buglet: cmp(a, a) does not raise TypeError, + # because __eq__ is tried before __cmp__, and a.__eq__(a) returns, + # which Python thinks is good enough to synthesize a cmp() result + # without calling __cmp__. + self.assertEqual(cmp(a, a), 0) + + self.assertRaises(TypeError, cmp, a, 12) + self.assertRaises(TypeError, cmp, "abc", a) #============================================================================== @@ -476,17 +485,19 @@ class TestSubsetNonOverlap(TestSubsets): class TestOnlySetsInBinaryOps(unittest.TestCase): - def test_cmp(self): - try: - self.other == self.set - self.fail("expected TypeError") - except TypeError: - pass - try: - self.set != self.other - self.fail("expected TypeError") - except TypeError: - pass + def test_eq_ne(self): + # Unlike the others, this is testing that == and != *are* allowed. + self.assertEqual(self.other == self.set, False) + self.assertEqual(self.set == self.other, False) + self.assertEqual(self.other != self.set, True) + self.assertEqual(self.set != self.other, True) + + def test_ge_gt_lt_le(self): + # Unlike the others, this is testing that == and != *are* allowed. + self.assertRaises(TypeError, lambda: self.set < self.other) + self.assertRaises(TypeError, lambda: self.set <= self.other) + self.assertRaises(TypeError, lambda: self.set > self.other) + self.assertRaises(TypeError, lambda: self.set >= self.other) def test_union_update(self): try: |