summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2008-08-11 15:45:58 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2008-08-11 15:45:58 (GMT)
commit48361f5cbf419cce361fd1aa0389d6304ad167db (patch)
treee3fb92982d5564830f6ce9a3f725acc51553ec50
parentf8d62d23e9b02c557b2bbe69f693fc14c2574281 (diff)
downloadcpython-48361f5cbf419cce361fd1aa0389d6304ad167db.zip
cpython-48361f5cbf419cce361fd1aa0389d6304ad167db.tar.gz
cpython-48361f5cbf419cce361fd1aa0389d6304ad167db.tar.bz2
Issue 2235: Py3k warnings are now emitted for classes that will no longer inherit a__hash__ implementation from a parent class in Python 3.x. The standard library and test suite have been updated to not emit these warnings.
-rw-r--r--Lib/UserList.py1
-rw-r--r--Lib/_abcoll.py6
-rw-r--r--Lib/ctypes/test/test_simplesubclasses.py2
-rw-r--r--Lib/numbers.py3
-rw-r--r--Lib/test/test_builtin.py1
-rw-r--r--Lib/test/test_coercion.py1
-rw-r--r--Lib/test/test_collections.py1
-rw-r--r--Lib/test/test_copy.py4
-rw-r--r--Lib/test/test_datetime.py1
-rw-r--r--Lib/test/test_descr.py5
-rw-r--r--Lib/test/test_hash.py24
-rw-r--r--Lib/test/test_operator.py1
-rw-r--r--Lib/test/test_py3kwarn.py92
-rw-r--r--Lib/test/test_slice.py1
-rw-r--r--Lib/test/test_sort.py1
-rw-r--r--Lib/unittest.py3
-rw-r--r--Lib/xml/dom/minidom.py1
-rw-r--r--Objects/typeobject.c35
18 files changed, 165 insertions, 18 deletions
diff --git a/Lib/UserList.py b/Lib/UserList.py
index 556a327..b445985 100644
--- a/Lib/UserList.py
+++ b/Lib/UserList.py
@@ -25,6 +25,7 @@ class UserList(collections.MutableSequence):
else: return other
def __cmp__(self, other):
return cmp(self.data, self.__cast(other))
+ __hash__ = None # Mutable sequence, so not hashable
def __contains__(self, item): return item in self.data
def __len__(self): return len(self.data)
def __getitem__(self, i): return self.data[i]
diff --git a/Lib/_abcoll.py b/Lib/_abcoll.py
index 85d733f..a5fee08 100644
--- a/Lib/_abcoll.py
+++ b/Lib/_abcoll.py
@@ -207,6 +207,9 @@ class Set(Sized, Iterable, Container):
other = self._from_iterable(other)
return (self - other) | (other - self)
+ # Sets are not hashable by default, but subclasses can change this
+ __hash__ = None
+
def _hash(self):
"""Compute the hash value of a set.
@@ -350,6 +353,9 @@ class Mapping(Sized, Iterable, Container):
def values(self):
return [self[key] for key in self]
+ # Mappings are not hashable by default, but subclasses can change this
+ __hash__ = None
+
def __eq__(self, other):
return isinstance(other, Mapping) and \
dict(self.items()) == dict(other.items())
diff --git a/Lib/ctypes/test/test_simplesubclasses.py b/Lib/ctypes/test/test_simplesubclasses.py
index 7155170..5671cce 100644
--- a/Lib/ctypes/test/test_simplesubclasses.py
+++ b/Lib/ctypes/test/test_simplesubclasses.py
@@ -6,6 +6,8 @@ class MyInt(c_int):
if type(other) != MyInt:
return -1
return cmp(self.value, other.value)
+ def __hash__(self): # Silence Py3k warning
+ return hash(self.value)
class Test(unittest.TestCase):
diff --git a/Lib/numbers.py b/Lib/numbers.py
index 38240d6..fa59fd8 100644
--- a/Lib/numbers.py
+++ b/Lib/numbers.py
@@ -18,6 +18,9 @@ class Number(object):
"""
__metaclass__ = ABCMeta
+ # Concrete numeric types must provide their own hash implementation
+ __hash__ = None
+
## Notes on Decimal
## ----------------
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 70980f8..6671f2c 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -1064,6 +1064,7 @@ class BuiltinTest(unittest.TestCase):
class badzero(int):
def __cmp__(self, other):
raise RuntimeError
+ __hash__ = None # Invalid cmp makes this unhashable
self.assertRaises(RuntimeError, range, a, a + 1, badzero(1))
# Reject floats when it would require PyLongs to represent.
diff --git a/Lib/test/test_coercion.py b/Lib/test/test_coercion.py
index e3a7e43..a70f82d 100644
--- a/Lib/test/test_coercion.py
+++ b/Lib/test/test_coercion.py
@@ -309,6 +309,7 @@ class CoercionTest(unittest.TestCase):
def __cmp__(slf, other):
self.assert_(other == 42, 'expected evil_coercer, got %r' % other)
return 0
+ __hash__ = None # Invalid cmp makes this unhashable
self.assertEquals(cmp(WackyComparer(), evil_coercer), 0)
# ...and classic classes too, since that code path is a little different
class ClassicWackyComparer:
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 99eb8cf..d689add 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -172,6 +172,7 @@ class TestOneTrickPonyABCs(unittest.TestCase):
class H(Hashable):
def __hash__(self):
return super(H, self).__hash__()
+ __eq__ = Hashable.__eq__ # Silence Py3k warning
self.assertEqual(hash(H()), 0)
self.failIf(issubclass(int, H))
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index d2899bd..be334cc 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -435,6 +435,7 @@ class TestCopy(unittest.TestCase):
return (C, (), self.__dict__)
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
+ __hash__ = None # Silence Py3k warning
x = C()
x.foo = [42]
y = copy.copy(x)
@@ -451,6 +452,7 @@ class TestCopy(unittest.TestCase):
self.__dict__.update(state)
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
+ __hash__ = None # Silence Py3k warning
x = C()
x.foo = [42]
y = copy.copy(x)
@@ -477,6 +479,7 @@ class TestCopy(unittest.TestCase):
def __cmp__(self, other):
return (cmp(list(self), list(other)) or
cmp(self.__dict__, other.__dict__))
+ __hash__ = None # Silence Py3k warning
x = C([[1, 2], 3])
y = copy.copy(x)
self.assertEqual(x, y)
@@ -494,6 +497,7 @@ class TestCopy(unittest.TestCase):
def __cmp__(self, other):
return (cmp(dict(self), list(dict)) or
cmp(self.__dict__, other.__dict__))
+ __hash__ = None # Silence Py3k warning
x = C([("foo", [1, 2]), ("bar", 3)])
y = copy.copy(x)
self.assertEqual(x, y)
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index cdc9eed..1674961 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -986,6 +986,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
# compare-by-address (which never says "equal" for distinct
# objects).
return 0
+ __hash__ = None # Silence Py3k warning
# This still errors, because date and datetime comparison raise
# TypeError instead of NotImplemented when they don't know what to
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 53b7611..f170d59 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1121,6 +1121,7 @@ order (MRO) for bases """
class G(object):
def __cmp__(self, other):
return 0
+ __hash__ = None # Silence Py3k warning
g = G()
orig_objects = len(gc.get_objects())
for i in xrange(10):
@@ -2727,6 +2728,7 @@ order (MRO) for bases """
if isinstance(other, int) or isinstance(other, long):
return cmp(self.value, other)
return NotImplemented
+ __hash__ = None # Silence Py3k warning
c1 = C(1)
c2 = C(2)
@@ -2755,6 +2757,7 @@ order (MRO) for bases """
return abs(self - other) <= 1e-6
except:
return NotImplemented
+ __hash__ = None # Silence Py3k warning
zz = ZZ(1.0000003)
self.assertEqual(zz, 1+0j)
self.assertEqual(1+0j, zz)
@@ -2767,6 +2770,7 @@ order (MRO) for bases """
self.value = int(value)
def __cmp__(self_, other):
self.fail("shouldn't call __cmp__")
+ __hash__ = None # Silence Py3k warning
def __eq__(self, other):
if isinstance(other, C):
return self.value == other.value
@@ -3262,6 +3266,7 @@ order (MRO) for bases """
class S(str):
def __eq__(self, other):
return self.lower() == other.lower()
+ __hash__ = None # Silence Py3k warning
def test_subclass_propagation(self):
# Testing propagation of slot functions to subclasses...
diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py
index f3954c2..47c66d1 100644
--- a/Lib/test/test_hash.py
+++ b/Lib/test/test_hash.py
@@ -52,6 +52,9 @@ class FixedHash(object):
class OnlyEquality(object):
def __eq__(self, other):
return self is other
+ # Trick to suppress Py3k warning in 2.x
+ __hash__ = None
+del OnlyEquality.__hash__
class OnlyInequality(object):
def __ne__(self, other):
@@ -60,6 +63,9 @@ class OnlyInequality(object):
class OnlyCmp(object):
def __cmp__(self, other):
return cmp(id(self), id(other))
+ # Trick to suppress Py3k warning in 2.x
+ __hash__ = None
+del OnlyCmp.__hash__
class InheritedHashWithEquality(FixedHash, OnlyEquality): pass
class InheritedHashWithInequality(FixedHash, OnlyInequality): pass
@@ -71,18 +77,15 @@ class NoHash(object):
class HashInheritanceTestCase(unittest.TestCase):
default_expected = [object(),
DefaultHash(),
+ OnlyEquality(),
+ OnlyInequality(),
+ OnlyCmp(),
]
fixed_expected = [FixedHash(),
InheritedHashWithEquality(),
InheritedHashWithInequality(),
InheritedHashWithCmp(),
]
- # TODO: Change these to expecting an exception
- # when forward porting to Py3k
- warning_expected = [OnlyEquality(),
- OnlyInequality(),
- OnlyCmp(),
- ]
error_expected = [NoHash()]
def test_default_hash(self):
@@ -93,20 +96,13 @@ class HashInheritanceTestCase(unittest.TestCase):
for obj in self.fixed_expected:
self.assertEqual(hash(obj), _FIXED_HASH_VALUE)
- def test_warning_hash(self):
- for obj in self.warning_expected:
- # TODO: Check for the expected Py3k warning here
- obj_hash = hash(obj)
- self.assertEqual(obj_hash, _default_hash(obj))
-
def test_error_hash(self):
for obj in self.error_expected:
self.assertRaises(TypeError, hash, obj)
def test_hashable(self):
objects = (self.default_expected +
- self.fixed_expected +
- self.warning_expected)
+ self.fixed_expected)
for obj in objects:
self.assert_(isinstance(obj, Hashable), repr(obj))
diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py
index 1c3fda3..9bc0a4e 100644
--- a/Lib/test/test_operator.py
+++ b/Lib/test/test_operator.py
@@ -57,6 +57,7 @@ class OperatorTestCase(unittest.TestCase):
class C(object):
def __eq__(self, other):
raise SyntaxError
+ __hash__ = None # Silence Py3k warning
self.failUnlessRaises(TypeError, operator.eq)
self.failUnlessRaises(SyntaxError, operator.eq, C(), C())
self.failIf(operator.eq(1, 0))
diff --git a/Lib/test/test_py3kwarn.py b/Lib/test/test_py3kwarn.py
index 67b2538..340e86f 100644
--- a/Lib/test/test_py3kwarn.py
+++ b/Lib/test/test_py3kwarn.py
@@ -12,6 +12,9 @@ if not sys.py3kwarning:
class TestPy3KWarnings(unittest.TestCase):
+ def assertWarning(self, _, warning, expected_message):
+ self.assertEqual(str(warning.message), expected_message)
+
def test_backquote(self):
expected = 'backquote not supported in 3.x; use repr()'
with catch_warning() as w:
@@ -28,30 +31,41 @@ class TestPy3KWarnings(unittest.TestCase):
with catch_warning() as w:
safe_exec("True = False")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("False = True")
self.assertWarning(None, w, expected)
+ w.reset()
try:
safe_exec("obj.False = True")
except NameError: pass
self.assertWarning(None, w, expected)
+ w.reset()
try:
safe_exec("obj.True = False")
except NameError: pass
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("def False(): pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("def True(): pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("class False: pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("class True: pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("def f(True=43): pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("def f(False=None): pass")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("f(False=True)")
self.assertWarning(None, w, expected)
+ w.reset()
safe_exec("f(True=1)")
self.assertWarning(None, w, expected)
@@ -60,20 +74,25 @@ class TestPy3KWarnings(unittest.TestCase):
expected = 'type inequality comparisons not supported in 3.x'
with catch_warning() as w:
self.assertWarning(int < str, w, expected)
+ w.reset()
self.assertWarning(type < object, w, expected)
def test_object_inequality_comparisons(self):
expected = 'comparing unequal types not supported in 3.x'
with catch_warning() as w:
self.assertWarning(str < [], w, expected)
+ w.reset()
self.assertWarning(object() < (1, 2), w, expected)
def test_dict_inequality_comparisons(self):
expected = 'dict inequality comparisons not supported in 3.x'
with catch_warning() as w:
self.assertWarning({} < {2:3}, w, expected)
+ w.reset()
self.assertWarning({} <= {}, w, expected)
+ w.reset()
self.assertWarning({} > {2:3}, w, expected)
+ w.reset()
self.assertWarning({2:3} >= {}, w, expected)
def test_cell_inequality_comparisons(self):
@@ -86,6 +105,7 @@ class TestPy3KWarnings(unittest.TestCase):
cell1, = f(1).func_closure
with catch_warning() as w:
self.assertWarning(cell0 == cell1, w, expected)
+ w.reset()
self.assertWarning(cell0 < cell1, w, expected)
def test_code_inequality_comparisons(self):
@@ -96,8 +116,11 @@ class TestPy3KWarnings(unittest.TestCase):
pass
with catch_warning() as w:
self.assertWarning(f.func_code < g.func_code, w, expected)
+ w.reset()
self.assertWarning(f.func_code <= g.func_code, w, expected)
+ w.reset()
self.assertWarning(f.func_code >= g.func_code, w, expected)
+ w.reset()
self.assertWarning(f.func_code > g.func_code, w, expected)
def test_builtin_function_or_method_comparisons(self):
@@ -107,13 +130,13 @@ class TestPy3KWarnings(unittest.TestCase):
meth = {}.get
with catch_warning() as w:
self.assertWarning(func < meth, w, expected)
+ w.reset()
self.assertWarning(func > meth, w, expected)
+ w.reset()
self.assertWarning(meth <= func, w, expected)
+ w.reset()
self.assertWarning(meth >= func, w, expected)
- def assertWarning(self, _, warning, expected_message):
- self.assertEqual(str(warning.message), expected_message)
-
def test_sort_cmp_arg(self):
expected = "the cmp argument is not supported in 3.x"
lst = range(5)
@@ -121,8 +144,11 @@ class TestPy3KWarnings(unittest.TestCase):
with catch_warning() as w:
self.assertWarning(lst.sort(cmp=cmp), w, expected)
+ w.reset()
self.assertWarning(sorted(lst, cmp=cmp), w, expected)
+ w.reset()
self.assertWarning(lst.sort(cmp), w, expected)
+ w.reset()
self.assertWarning(sorted(lst, cmp), w, expected)
def test_sys_exc_clear(self):
@@ -156,7 +182,7 @@ class TestPy3KWarnings(unittest.TestCase):
self.assertWarning(None, w, expected)
def test_buffer(self):
- expected = 'buffer() not supported in 3.x; use memoryview()'
+ expected = 'buffer() not supported in 3.x'
with catch_warning() as w:
self.assertWarning(buffer('a'), w, expected)
@@ -167,6 +193,64 @@ class TestPy3KWarnings(unittest.TestCase):
with catch_warning() as w:
self.assertWarning(f.xreadlines(), w, expected)
+ def test_hash_inheritance(self):
+ with catch_warning() as w:
+ # With object as the base class
+ class WarnOnlyCmp(object):
+ def __cmp__(self, other): pass
+ self.assertEqual(len(w.warnings), 1)
+ self.assertWarning(None, w,
+ "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class WarnOnlyEq(object):
+ def __eq__(self, other): pass
+ self.assertEqual(len(w.warnings), 1)
+ self.assertWarning(None, w,
+ "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class WarnCmpAndEq(object):
+ def __cmp__(self, other): pass
+ def __eq__(self, other): pass
+ self.assertEqual(len(w.warnings), 2)
+ self.assertWarning(None, w.warnings[-2],
+ "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+ self.assertWarning(None, w,
+ "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class NoWarningOnlyHash(object):
+ def __hash__(self): pass
+ self.assertEqual(len(w.warnings), 0)
+ # With an intermediate class in the heirarchy
+ class DefinesAllThree(object):
+ def __cmp__(self, other): pass
+ def __eq__(self, other): pass
+ def __hash__(self): pass
+ class WarnOnlyCmp(DefinesAllThree):
+ def __cmp__(self, other): pass
+ self.assertEqual(len(w.warnings), 1)
+ self.assertWarning(None, w,
+ "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class WarnOnlyEq(DefinesAllThree):
+ def __eq__(self, other): pass
+ self.assertEqual(len(w.warnings), 1)
+ self.assertWarning(None, w,
+ "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class WarnCmpAndEq(DefinesAllThree):
+ def __cmp__(self, other): pass
+ def __eq__(self, other): pass
+ self.assertEqual(len(w.warnings), 2)
+ self.assertWarning(None, w.warnings[-2],
+ "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+ self.assertWarning(None, w,
+ "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+ w.reset()
+ class NoWarningOnlyHash(DefinesAllThree):
+ def __hash__(self): pass
+ self.assertEqual(len(w.warnings), 0)
+
+
class TestStdlibRemovals(unittest.TestCase):
diff --git a/Lib/test/test_slice.py b/Lib/test/test_slice.py
index 8c90c10..854805b 100644
--- a/Lib/test/test_slice.py
+++ b/Lib/test/test_slice.py
@@ -33,6 +33,7 @@ class SliceTest(unittest.TestCase):
class BadCmp(object):
def __eq__(self, other):
raise Exc
+ __hash__ = None # Silence Py3k warning
s1 = slice(BadCmp())
s2 = slice(BadCmp())
diff --git a/Lib/test/test_sort.py b/Lib/test/test_sort.py
index 84c92cc..a61fb96 100644
--- a/Lib/test/test_sort.py
+++ b/Lib/test/test_sort.py
@@ -70,6 +70,7 @@ class TestBase(unittest.TestCase):
def __cmp__(self, other):
return cmp(self.key, other.key)
+ __hash__ = None # Silence Py3k warning
def __repr__(self):
return "Stable(%d, %d)" % (self.key, self.index)
diff --git a/Lib/unittest.py b/Lib/unittest.py
index b5a1a4b..09c6ca9 100644
--- a/Lib/unittest.py
+++ b/Lib/unittest.py
@@ -425,6 +425,9 @@ class TestSuite:
def __ne__(self, other):
return not self == other
+ # Can't guarantee hash invariant, so flag as unhashable
+ __hash__ = None
+
def __iter__(self):
return iter(self._tests)
diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py
index 9f7f62c..ad42947 100644
--- a/Lib/xml/dom/minidom.py
+++ b/Lib/xml/dom/minidom.py
@@ -516,6 +516,7 @@ class NamedNodeMap(object):
__len__ = _get_length
+ __hash__ = None # Mutable type can't be correctly hashed
def __cmp__(self, other):
if self._attrs is getattr(other, "_attrs", None):
return 0
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 0af3f30..42974f8 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3648,6 +3648,22 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
}
+static int
+overrides_name(PyTypeObject *type, char *name)
+{
+ PyObject *dict = type->tp_dict;
+
+ assert(dict != NULL);
+ if (PyDict_GetItemString(dict, name) != NULL) {
+ return 1;
+ }
+ return 0;
+}
+
+#define OVERRIDES_HASH(x) overrides_name(x, "__hash__")
+#define OVERRIDES_CMP(x) overrides_name(x, "__cmp__")
+#define OVERRIDES_EQ(x) overrides_name(x, "__eq__")
+
static void
inherit_slots(PyTypeObject *type, PyTypeObject *base)
{
@@ -3786,6 +3802,25 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
type->tp_compare = base->tp_compare;
type->tp_richcompare = base->tp_richcompare;
type->tp_hash = base->tp_hash;
+ /* Check for changes to inherited methods in Py3k*/
+ if (Py_Py3kWarningFlag) {
+ if (base->tp_hash &&
+ (base->tp_hash != PyObject_HashNotImplemented) &&
+ !OVERRIDES_HASH(type)) {
+ if (OVERRIDES_CMP(type)) {
+ PyErr_WarnPy3k("Overriding "
+ "__cmp__ blocks inheritance "
+ "of __hash__ in 3.x",
+ 1);
+ }
+ if (OVERRIDES_EQ(type)) {
+ PyErr_WarnPy3k("Overriding "
+ "__eq__ blocks inheritance "
+ "of __hash__ in 3.x",
+ 1);
+ }
+ }
+ }
}
}
else {