https://github.com/python/cpython/commit/bd83de61433a8f6e5ae49be6ec54dc9f7d9e6aaf
commit: bd83de61433a8f6e5ae49be6ec54dc9f7d9e6aaf
branch: main
author: Ivan Zorin <[email protected]>
committer: vstinner <[email protected]>
date: 2026-11-07T16:07:27+01:00
summary:
gh-161433: Identify the failing item when hashing tuples (#161496)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
M Lib/test/test_tuple.py
M Misc/ACKS
M Objects/tupleobject.c
diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py
index 5650dec09adef4..a3bd60eca4f6b1 100644
--- a/Lib/test/test_tuple.py
+++ b/Lib/test/test_tuple.py
@@ -450,6 +450,45 @@ def test_lexicographic_ordering(self):
self.assertLess(a, b)
self.assertLess(b, c)
+ def test_hash_unhashable_note(self):
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'list'") as
cm:
+ hash(("alice", []))
+ self.assertEqual(cm.exception.__notes__,
+ ["when hashing tuple item 1"])
+
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'list'") as
cm:
+ hash((("alice", []), 42))
+ self.assertEqual(cm.exception.__notes__,
+ ["when hashing tuple item 1",
+ "when hashing tuple item 0"])
+
+ class BrokenHash:
+ def __hash__(self):
+ raise TypeError("custom failure")
+
+ with self.assertRaisesRegex(TypeError, "custom failure") as cm:
+ hash((BrokenHash(),))
+ self.assertEqual(cm.exception.__notes__, ["when hashing tuple item 0"])
+
+ class BrokenHashNote:
+ def __hash__(self):
+ exc = TypeError("custom failure")
+ exc.add_note("from custom hash")
+ raise exc
+
+ with self.assertRaisesRegex(TypeError, "custom failure") as cm:
+ hash((BrokenHashNote(),))
+ self.assertEqual(cm.exception.__notes__,
+ ["from custom hash", "when hashing tuple item 0"])
+
+ class BrokenHashValueError:
+ def __hash__(self):
+ raise ValueError("custom failure")
+
+ with self.assertRaisesRegex(ValueError, "custom failure") as cm:
+ hash((BrokenHashValueError(),))
+ self.assertFalse(hasattr(cm.exception, "__notes__"))
+
# Notes on testing hash codes. The primary thing is that Python doesn't
# care about "random" hash codes. To the contrary, we like them to be
# very regular when possible, so that the low-order bits are as evenly
diff --git a/Misc/ACKS b/Misc/ACKS
index 65f6f423eb7c42..4178ae3108128a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -2048,6 +2048,7 @@ Sjoerd de Vries
Jonas Wagner
Daniel Wagner-Hall
Niki W. Waibel
+WakaTaira
Wojtek Walczak
Charles Waldman
Richard Walker
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
new file mode 100644
index 00000000000000..251837ac6d632a
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
@@ -0,0 +1,2 @@
+Add context notes to :exc:`TypeError` raised while hashing a
+:class:`tuple`, identifying the index of the item whose hash failed.
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 68ca2772507295..992699756ac770 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -8,6 +8,7 @@
#include "pycore_list.h" // _Py_memory_repeat()
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
#include "pycore_object.h" // _PyObject_GC_TRACK()
+#include "pycore_pyerrors.h" // _PyException_AddNote()
#include "pycore_stackref.h" // PyStackRef_AsPyObjectSteal()
#include "pycore_tuple.h" // _PyTupleIterObject
@@ -392,6 +393,23 @@ tuple_hash(PyObject *op)
for (Py_ssize_t i = 0; i < len; i++) {
Py_uhash_t lane = PyObject_Hash(item[i]);
if (lane == (Py_uhash_t)-1) {
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ /* Preserve the original error while providing context for
+ the element that could not be hashed. */
+ PyObject *exc = PyErr_GetRaisedException();
+ PyObject *note = PyUnicode_FromFormat(
+ "when hashing tuple item %zd", i);
+ if (note != NULL) {
+ if (_PyException_AddNote(exc, note) < 0) {
+ PyErr_Clear();
+ }
+ Py_DECREF(note);
+ }
+ else {
+ PyErr_Clear();
+ }
+ PyErr_SetRaisedException(exc);
+ }
_Py_LeaveRecursiveCall();
return -1;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]