Skip to content

bpo-20524: adds better error message for .format() #28310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,5 +519,33 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self):
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't noticed this test before. It's great, especially because of the work in using "kind" to compute the actual error message.

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()
Original file line number Diff line number Diff line change
@@ -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.
26 changes: 19 additions & 7 deletions Python/formatter_unicode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down