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__.
This commit is contained in:
Tim Peters 2003-03-02 00:19:49 +00:00
parent 3ba491e6b1
commit 44f14b0399
3 changed files with 61 additions and 23 deletions

View File

@ -102,20 +102,40 @@ class BaseSet(object):
""" """
return self._data.iterkeys() 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): def __cmp__(self, other):
raise TypeError, "can't compare sets using cmp()" 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): def __eq__(self, other):
self._binary_sanity_check(other) if isinstance(other, BaseSet):
return self._data == other._data return self._data == other._data
else:
return False
def __ne__(self, other): def __ne__(self, other):
self._binary_sanity_check(other) if isinstance(other, BaseSet):
return self._data != other._data return self._data != other._data
else:
return True
# Copying operations # Copying operations

View File

@ -232,7 +232,16 @@ class TestBinaryOps(unittest.TestCase):
def test_cmp(self): def test_cmp(self):
a, b = Set('a'), Set('b') 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): class TestOnlySetsInBinaryOps(unittest.TestCase):
def test_cmp(self): def test_eq_ne(self):
try: # Unlike the others, this is testing that == and != *are* allowed.
self.other == self.set self.assertEqual(self.other == self.set, False)
self.fail("expected TypeError") self.assertEqual(self.set == self.other, False)
except TypeError: self.assertEqual(self.other != self.set, True)
pass self.assertEqual(self.set != self.other, True)
try:
self.set != self.other def test_ge_gt_lt_le(self):
self.fail("expected TypeError") # Unlike the others, this is testing that == and != *are* allowed.
except TypeError: self.assertRaises(TypeError, lambda: self.set < self.other)
pass 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): def test_union_update(self):
try: try:

View File

@ -31,6 +31,13 @@ Extension modules
Library Library
------- -------
- sets.Set objects now support mixed-type __eq__ and __ne__, instead
of raising TypeError. If x is a Set object and y is a non-Set object,
x == y is False, and x != y is True. This is akin to the change made
for mixed-type comparisons of datetime objects in 2.3a2; more info
about the rationale is in the NEWS entry for that. See also SF bug
report <http://www.python.org/sf/693121>.
- os.listdir() now returns Unicode strings on platforms that set - os.listdir() now returns Unicode strings on platforms that set
Py_FileSystemDefaultEncoding, for file names that are not representable Py_FileSystemDefaultEncoding, for file names that are not representable
in ASCII. (This currently only affects MacOS X; on Windows versions in ASCII. (This currently only affects MacOS X; on Windows versions
@ -83,7 +90,7 @@ Mac
- A new method MacOS.WMAvailable() returns true if it is safe to access - A new method MacOS.WMAvailable() returns true if it is safe to access
the window manager, false otherwise. the window manager, false otherwise.
- EasyDialogs dialogs are now movable-modal. - EasyDialogs dialogs are now movable-modal.
@ -343,8 +350,8 @@ Library
- the platform dependent path related variables sep, altsep, extsep, - the platform dependent path related variables sep, altsep, extsep,
pathsep, curdir, pardir and defpath are now defined in the platform pathsep, curdir, pardir and defpath are now defined in the platform
dependent path modules (e.g. ntpath.py) rather than os.py, so these dependent path modules (e.g. ntpath.py) rather than os.py, so these
variables are now available via os.path. They continue to be variables are now available via os.path. They continue to be
available from the os module. available from the os module.
(see <http://www.python.org/sf/680789>). (see <http://www.python.org/sf/680789>).
- array.array was added to the types repr.py knows about (see - array.array was added to the types repr.py knows about (see
@ -499,12 +506,12 @@ Mac
- Type Carbon.File.FSCatalogInfo and supporting methods have been implemented. - Type Carbon.File.FSCatalogInfo and supporting methods have been implemented.
This also makes macfs.FSSpec.SetDates() work again. This also makes macfs.FSSpec.SetDates() work again.
- There is a new module pimp, the package install manager for Python, and - There is a new module pimp, the package install manager for Python, and
accompanying applet PackageManager. These allow you to easily download accompanying applet PackageManager. These allow you to easily download
and install pretested extension packages either in source or binary and install pretested extension packages either in source or binary
form. Only in MacPython-OSX. form. Only in MacPython-OSX.
- Applets are now built with bundlebuilder in MacPython-OSX, which should make - Applets are now built with bundlebuilder in MacPython-OSX, which should make
them more robust and also provides a path towards BuildApplication. The them more robust and also provides a path towards BuildApplication. The
downside of this change is that applets can no longer be run from the downside of this change is that applets can no longer be run from the