Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 83688) +++ Objects/dictobject.c (working copy) @@ -10,15 +10,16 @@ #include "Python.h" #include "stringlib/eq.h" +static PyObject *keyerror_message = NULL; -/* Set a key error with the specified argument, wrapping it in a +/* Set a key error with the specified arguments, wrapping it in a * tuple automatically so that tuple keys are not unpacked as the * exception arguments. */ static void set_key_error(PyObject *arg) { PyObject *tup; - tup = PyTuple_Pack(1, arg); + tup = PyTuple_Pack(2, keyerror_message, arg); if (!tup) return; /* caller will expect error to be set anyway */ PyErr_SetObject(PyExc_KeyError, tup); @@ -246,6 +247,9 @@ dummy = PyUnicode_FromString(""); if (dummy == NULL) return NULL; + keyerror_message = PyUnicode_FromString("Not in dict"); + if (keyerror_message == NULL) + return NULL; #ifdef SHOW_CONVERSION_COUNTS Py_AtExit(show_counts); #endif Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (revision 83688) +++ Objects/exceptions.c (working copy) @@ -1082,17 +1082,24 @@ static PyObject * KeyError_str(PyBaseExceptionObject *self) { - /* If args is a tuple of exactly one item, apply repr to args[0]. + /* If args is a tuple of exactly two items, the first is the message, and + the second is the offending key, with %r formatting applied. This is done so that e.g. the exception raised by {}[''] prints KeyError: '' rather than the confusing KeyError - alone. The downside is that if KeyError is raised with an explanatory - string, that string will be displayed in quotes. Too bad. + alone. If args is anything else, use the default BaseException__str__(). */ - if (PyTuple_GET_SIZE(self->args) == 1) { - return PyObject_Repr(PyTuple_GET_ITEM(self->args, 0)); + if (PyTuple_GET_SIZE(self->args) == 2) { + PyObject *fmt, *ret; + fmt = PyUnicode_FromString("%s: %r"); + if (!fmt) + return NULL; + + ret = PyUnicode_Format(fmt, self->args); + Py_DECREF(fmt); + return ret; } return BaseException_str(self); } Index: Objects/setobject.c =================================================================== --- Objects/setobject.c (revision 83688) +++ Objects/setobject.c (working copy) @@ -18,7 +18,14 @@ set_key_error(PyObject *arg) { PyObject *tup; - tup = PyTuple_Pack(1, arg); + static PyObject *keyerror_message = NULL; + if (!keyerror_message) + { + keyerror_message = PyUnicode_FromString("Not in set"); + if (!keyerror_message) + return; + } + tup = PyTuple_Pack(2, keyerror_message, arg); if (!tup) return; /* caller will expect error to be set anyway */ PyErr_SetObject(PyExc_KeyError, tup); Index: Lib/configparser.py =================================================================== --- Lib/configparser.py (revision 83688) +++ Lib/configparser.py (working copy) @@ -735,7 +735,7 @@ value = value % vars except KeyError as e: raise InterpolationMissingOptionError( - option, section, rawval, e.args[0]) + option, section, rawval, e.args[-1]) else: break if value and "%(" in value: Index: Lib/_abcoll.py =================================================================== --- Lib/_abcoll.py (revision 83688) +++ Lib/_abcoll.py (working copy) @@ -289,7 +289,7 @@ def remove(self, value): """Remove an element. If not a member, raise a KeyError.""" if value not in self: - raise KeyError(value) + raise KeyError("Not in set", value) self.discard(value) def pop(self): Index: Lib/collections.py =================================================================== --- Lib/collections.py (revision 83688) +++ Lib/collections.py (working copy) @@ -571,7 +571,7 @@ return self.data[key] if hasattr(self.__class__, "__missing__"): return self.__class__.__missing__(self, key) - raise KeyError(key) + raise KeyError("Not in dict", key) def __setitem__(self, key, item): self.data[key] = item def __delitem__(self, key): del self.data[key] def __iter__(self): Index: Lib/test/test_dict.py =================================================================== --- Lib/test/test_dict.py (revision 83688) +++ Lib/test/test_dict.py (working copy) @@ -546,21 +546,21 @@ f = F() with self.assertRaises(KeyError) as c: f[42] - self.assertEqual(c.exception.args, (42,)) + self.assertEqual(c.exception.args, ("Not in dict", 42,)) class G(dict): pass g = G() with self.assertRaises(KeyError) as c: g[42] - self.assertEqual(c.exception.args, (42,)) + self.assertEqual(c.exception.args, ("Not in dict", 42,)) def test_tuple_keyerror(self): # SF #1576657 d = {} with self.assertRaises(KeyError) as c: d[(1,)] - self.assertEqual(c.exception.args, ((1,),)) + self.assertEqual(c.exception.args, ("Not in dict", (1,),)) def test_bad_key(self): # Dictionary lookups should fail if __eq__() raises an exception. Index: Lib/test/test_set.py =================================================================== --- Lib/test/test_set.py (revision 83688) +++ Lib/test/test_set.py (working copy) @@ -402,21 +402,19 @@ try: self.s.remove(v1) except KeyError as e: - v2 = e.args[0] + v2 = e.args[1] self.assertEqual(v1, v2) else: self.fail() def test_remove_keyerror_set(self): key = self.thetype([3, 4]) - try: + with self.assertRaises(KeyError) as c: self.s.remove(key) - except KeyError as e: - self.assertTrue(e.args[0] is key, - "KeyError should be {0}, not {1}".format(key, - e.args[0])) - else: - self.fail() + e = c.exception + self.assertTrue(e.args[1] is key, + "KeyError should be {0}, not {1}".format(key, + e.args[1])) def test_discard(self): self.s.discard('a') Index: Lib/test/test_defaultdict.py =================================================================== --- Lib/test/test_defaultdict.py (revision 83688) +++ Lib/test/test_defaultdict.py (working copy) @@ -42,12 +42,9 @@ self.assertNotIn(12, d2.keys()) d2.default_factory = None self.assertEqual(d2.default_factory, None) - try: + with self.assertRaises(KeyError) as c: d2[15] - except KeyError as err: - self.assertEqual(err.args, (15,)) - else: - self.fail("d2[15] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 15,)) self.assertRaises(TypeError, defaultdict, 1) def test_missing(self): @@ -142,12 +139,9 @@ def test_keyerror_without_factory(self): d1 = defaultdict() - try: + with self.assertRaises(KeyError) as c: d1[(1,)] - except KeyError as err: - self.assertEqual(err.args[0], (1,)) - else: - self.fail("expected KeyError") + self.assertEqual(c.exception.args[1], (1,)) def test_recursive_repr(self): # Issue2045: stack overflow when default_factory is a bound method Index: Lib/test/test_userdict.py =================================================================== --- Lib/test/test_userdict.py (revision 83688) +++ Lib/test/test_userdict.py (working copy) @@ -171,21 +171,15 @@ self.__missing__ = lambda key: None collections.UserDict.__init__(self) f = F() - try: + with self.assertRaises(KeyError) as c: f[42] - except KeyError as err: - self.assertEqual(err.args, (42,)) - else: - self.fail("f[42] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 42,)) class G(collections.UserDict): pass g = G() - try: + with self.assertRaises(KeyError) as c: g[42] - except KeyError as err: - self.assertEqual(err.args, (42,)) - else: - self.fail("g[42] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 42,)) Index: Modules/_collectionsmodule.c =================================================================== --- Modules/_collectionsmodule.c (revision 83688) +++ Modules/_collectionsmodule.c (working copy) @@ -1258,7 +1258,7 @@ PyDoc_STRVAR(defdict_missing_doc, "__missing__(key) # Called by __getitem__ for missing key; pseudo-code:\n\ - if self.default_factory is None: raise KeyError((key,))\n\ + if self.default_factory is None: raise KeyError('Not in dict', key)\n\ self[key] = value = self.default_factory()\n\ return value\n\ "); @@ -1270,9 +1270,17 @@ PyObject *value; if (factory == NULL || factory == Py_None) { /* XXX Call dict.__missing__(key) */ + static PyObject *keyerror_message = NULL; PyObject *tup; - tup = PyTuple_Pack(1, key); - if (!tup) return NULL; + if (!keyerror_message) + { + keyerror_message = PyUnicode_FromString("Not in dict"); + if (!keyerror_message) + return NULL; + } + tup = PyTuple_Pack(2, keyerror_message, key); + if (!tup) + return NULL; PyErr_SetObject(PyExc_KeyError, tup); Py_DECREF(tup); return NULL;