diff options
author | Nikita Sobolev <mail@sobolevn.me> | 2021-09-24 18:18:04 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-24 11:18:04 -0400 |
commit | 8d8729146f21f61af66e70d3ae9501ea6bdccd09 (patch) | |
tree | ed5905df6eb9d2bbab7513b966039cc0642635ca | |
parent | bpo-30951: Correct co_names docstring in inspect module (GH-2743) (diff) | |
download | cpython-8d8729146f21f61af66e70d3ae9501ea6bdccd09.tar.gz cpython-8d8729146f21f61af66e70d3ae9501ea6bdccd09.tar.bz2 cpython-8d8729146f21f61af66e70d3ae9501ea6bdccd09.zip |
bpo-20524: adds better error message for `.format()` (GH-28310)
It now lists the bad format_spec and the type of the object.
-rw-r--r-- | Lib/test/test_format.py | 28 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-09-13-14-59-01.bpo-20524.PMQ1Fh.rst | 3 | ||||
-rw-r--r-- | Python/formatter_unicode.c | 26 |
3 files changed, 50 insertions, 7 deletions
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index ae0d4f7308e..16d29d1ea3d 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -519,5 +519,33 @@ class FormatTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, error_msg): '{:_,}'.format(1) + def test_better_error_message_format(self): + # https://bugs.python.org/issue20524 + for value in [12j, 12, 12.0, "12"]: + with self.subTest(value=value): + # The format spec must be invalid for all types we're testing. + # '%M' will suffice. + bad_format_spec = '%M' + err = re.escape("Invalid format specifier " + f"'{bad_format_spec}' for object of type " + f"'{type(value).__name__}'") + with self.assertRaisesRegex(ValueError, err): + f"xx{{value:{bad_format_spec}}}yy".format(value=value) + + # Also test the builtin format() function. + with self.assertRaisesRegex(ValueError, err): + format(value, bad_format_spec) + + # Also test f-strings. + with self.assertRaisesRegex(ValueError, err): + eval("f'xx{value:{bad_format_spec}}yy'") + + def test_unicode_in_error_message(self): + str_err = re.escape( + "Invalid format specifier '%ЫйЯЧ' for object of type 'str'") + with self.assertRaisesRegex(ValueError, str_err): + "{a:%ЫйЯЧ}".format(a='a') + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2021-09-13-14-59-01.bpo-20524.PMQ1Fh.rst b/Misc/NEWS.d/next/Library/2021-09-13-14-59-01.bpo-20524.PMQ1Fh.rst new file mode 100644 index 00000000000..9a41c8e64f7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-09-13-14-59-01.bpo-20524.PMQ1Fh.rst @@ -0,0 +1,3 @@ +Improves error messages on ``.format()`` operation for ``str``, ``float``, +``int``, and ``complex``. New format now shows the problematic pattern and +the object type. diff --git a/Python/formatter_unicode.c b/Python/formatter_unicode.c index 7b5a7bd04eb..9071bf3a658 100644 --- a/Python/formatter_unicode.c +++ b/Python/formatter_unicode.c @@ -162,7 +162,8 @@ DEBUG_PRINT_FORMAT_SPEC(InternalFormatSpec *format) if failure, sets the exception */ static int -parse_internal_render_format_spec(PyObject *format_spec, +parse_internal_render_format_spec(PyObject *obj, + PyObject *format_spec, Py_ssize_t start, Py_ssize_t end, InternalFormatSpec *format, char default_type, @@ -279,8 +280,19 @@ parse_internal_render_format_spec(PyObject *format_spec, /* Finally, parse the type field. */ if (end-pos > 1) { - /* More than one char remain, invalid format specifier. */ - PyErr_Format(PyExc_ValueError, "Invalid format specifier"); + /* More than one char remains, so this is an invalid format + specifier. */ + /* Create a temporary object that contains the format spec we're + operating on. It's format_spec[start:end] (in Python syntax). */ + PyObject* actual_format_spec = PyUnicode_FromKindAndData(kind, + (char*)data + kind*start, + end-start); + if (actual_format_spec != NULL) { + PyErr_Format(PyExc_ValueError, + "Invalid format specifier '%U' for object of type '%.200s'", + actual_format_spec, Py_TYPE(obj)->tp_name); + Py_DECREF(actual_format_spec); + } return 0; } @@ -1444,7 +1456,7 @@ _PyUnicode_FormatAdvancedWriter(_PyUnicodeWriter *writer, } /* parse the format_spec */ - if (!parse_internal_render_format_spec(format_spec, start, end, + if (!parse_internal_render_format_spec(obj, format_spec, start, end, &format, 's', '<')) return -1; @@ -1480,7 +1492,7 @@ _PyLong_FormatAdvancedWriter(_PyUnicodeWriter *writer, } /* parse the format_spec */ - if (!parse_internal_render_format_spec(format_spec, start, end, + if (!parse_internal_render_format_spec(obj, format_spec, start, end, &format, 'd', '>')) goto done; @@ -1536,7 +1548,7 @@ _PyFloat_FormatAdvancedWriter(_PyUnicodeWriter *writer, return format_obj(obj, writer); /* parse the format_spec */ - if (!parse_internal_render_format_spec(format_spec, start, end, + if (!parse_internal_render_format_spec(obj, format_spec, start, end, &format, '\0', '>')) return -1; @@ -1575,7 +1587,7 @@ _PyComplex_FormatAdvancedWriter(_PyUnicodeWriter *writer, return format_obj(obj, writer); /* parse the format_spec */ - if (!parse_internal_render_format_spec(format_spec, start, end, + if (!parse_internal_render_format_spec(obj, format_spec, start, end, &format, '\0', '>')) return -1; |