Index: Objects/unicodeobject.c =================================================================== --- Objects/unicodeobject.c (revision 85697) +++ Objects/unicodeobject.c (working copy) @@ -9214,6 +9214,13 @@ return -1; } + +/* Constants for the type of % argument being used -- not known, + a mapping, or a tuple. */ +#define FORMAT_TYPE_UNKNOWN 0 +#define FORMAT_TYPE_MAPPING 1 +#define FORMAT_TYPE_TUPLE 2 + /* fmt%(v1,v2,...) is roughly equivalent to sprintf(fmt, v1, v2, ...) FORMATBUFLEN is the length of the buffer in which chars are formatted. */ @@ -9225,6 +9232,7 @@ Py_UNICODE *fmt, *res; Py_ssize_t fmtcnt, rescnt, reslen, arglen, argidx; int args_owned = 0; + int format_type = FORMAT_TYPE_UNKNOWN; PyUnicodeObject *result = NULL; PyObject *dict = NULL; PyObject *uformat; @@ -9290,7 +9298,13 @@ Py_ssize_t keylen; PyObject *key; int pcount = 1; - + if (format_type == FORMAT_TYPE_TUPLE) { + PyErr_SetString(PyExc_ValueError, + "both keyed and unkeyed format " + "specifiers used"); + goto onError; + } + format_type = FORMAT_TYPE_MAPPING; if (dict == NULL) { PyErr_SetString(PyExc_TypeError, "format requires a mapping"); @@ -9339,6 +9353,19 @@ arglen = -1; argidx = -2; } + /* Check if it's really format specifier + and we are not at the end of the line */ + else if (*fmt != '%' && fmtcnt != 0) { + if (format_type == FORMAT_TYPE_MAPPING) { + PyErr_SetString(PyExc_ValueError, + "both keyed and unkeyed format " + "specifiers used"); + goto onError; + } + else if (format_type == FORMAT_TYPE_UNKNOWN) { + format_type = FORMAT_TYPE_TUPLE; + } + } while (--fmtcnt >= 0) { switch (c = *fmt++) { case '-': flags |= F_LJUST; continue; Index: Lib/test/test_unicode.py =================================================================== --- Lib/test/test_unicode.py (revision 85697) +++ Lib/test/test_unicode.py (working copy) @@ -749,6 +749,19 @@ self.assertRaises(TypeError, "%c".__mod__, "aa") self.assertRaises(ValueError, "%.1\u1032f".__mod__, (1.0/3)) + # dict and tuples mixing: + self.assertRaises(ValueError, '%(a)s %s'.__mod__, {'a':'xyz'}) + self.assertRaises(ValueError, '%s %(a)s'.__mod__, ('a',)) + + # wrong mapping: + self.assertRaises(TypeError, '%(a)s %s'.__mod__, ('a',)) + + # ending '%' sign: + self.assertEqual('%s %%' % ('a',), 'a %') + + # incomplite format: + self.assertRaises(ValueError, '%s %'.__mod__, 'a') + # formatting jobs delegated from the string implementation: self.assertEqual('...%(foo)s...' % {'foo':"abc"}, '...abc...') self.assertEqual('...%(foo)s...' % {'foo':"abc"}, '...abc...')