Skip to content
Open
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
20 changes: 10 additions & 10 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,16 +781,16 @@ def __int__(self):
pi = PseudoFloat(3.1415)

exceptions_params = [
('%x format: an integer is required, not float', b'%x', 3.14),
('%X format: an integer is required, not float', b'%X', 2.11),
('%o format: an integer is required, not float', b'%o', 1.79),
('%x format: an integer is required, not PseudoFloat', b'%x', pi),
('%x format: an integer is required, not complex', b'%x', 3j),
('%X format: an integer is required, not complex', b'%X', 2j),
('%o format: an integer is required, not complex', b'%o', 1j),
('%u format: a real number is required, not complex', b'%u', 3j),
('%i format: a real number is required, not complex', b'%i', 2j),
('%d format: a real number is required, not complex', b'%d', 2j),
('%x requires an integer, not float', b'%x', 3.14),
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer to keep the word "format", like:

Suggested change
('%x requires an integer, not float', b'%x', 3.14),
('%x format requires an integer, not float', b'%x', 3.14),

Copy link
Member Author

Choose a reason for hiding this comment

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

I removed "format" because in "format argument 1: %x format requires an integer, not float" it would be repeated twice.

We can add the "format argument" prefix (without argument number or name) for the case when the operand is not a tuple or dict: "format argument: %x requires an integer, not float".

Copy link
Member

Choose a reason for hiding this comment

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

The error message doesn't contain format argument 1: :

>>> "%x" % "abc"
TypeError: %x requires an integer, not str

In which case format argument 1: is added?

Copy link
Member Author

@serhiy-storchaka serhiy-storchaka Jan 16, 2026

Choose a reason for hiding this comment

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

When you have several format arguments. For example:

>>> "%x %s" % ("abc", 42)
TypeError: format argument 1: %x requires an integer, not str
>>> "%(a)x %(b)s" % {"a": "abc", "b": 42}
TypeError: format argument 'a': %x requires an integer, not str

We can add it for single non-tuple argument:

>>> "%x" % "abc"
TypeError: format argument: %x requires an integer, not str

What would be better?

Copy link
Member

Choose a reason for hiding this comment

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

format argument: %x requires an integer, not str is better than just %x requires an integer, not str. It gives more context.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added. How is now?

('%X requires an integer, not float', b'%X', 2.11),
('%o requires an integer, not float', b'%o', 1.79),
(r'%x requires an integer, not .*\.PseudoFloat', b'%x', pi),
('%x requires an integer, not complex', b'%x', 3j),
('%X requires an integer, not complex', b'%X', 2j),
('%o requires an integer, not complex', b'%o', 1j),
('%u requires a real number, not complex', b'%u', 3j),
('%i requires a real number, not complex', b'%i', 2j),
('%d requires a real number, not complex', b'%d', 2j),
(
r'%c requires an integer in range\(256\)'
r' or a single byte, not .*\.PseudoFloat',
Expand Down
239 changes: 197 additions & 42 deletions Lib/test/test_format.py

Large diffs are not rendered by default.

19 changes: 12 additions & 7 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,22 +733,27 @@ def test_format_errors(self):
with self.assertRaisesRegex(TypeError,
'not all arguments converted during string formatting'):
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
with self.assertRaisesRegex(ValueError, 'incomplete format'):
with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
Copy link
Member

Choose a reason for hiding this comment

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

You might keep the word "format", like:

Suggested change
with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
with self.assertRaisesRegex(ValueError, 'incomplete format: stray % at position 2'):

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that stray % is more common error than incomplete format.

eval("'%s%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(ValueError, 'incomplete format'):
with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
eval("'%s%%%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
eval("'%s%z' % (x,)", {'x': 1234})
with self.assertRaisesRegex(ValueError, 'unsupported format character'):
with self.assertRaisesRegex(ValueError,
'unsupported format %z at position 2'):
eval("'%s%z' % (x, 5)", {'x': 1234})
with self.assertRaisesRegex(TypeError, 'a real number is required, not str'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %d requires a real number, not str'):
eval("'%d' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %x requires an integer, not float'):
eval("'%x' % (x,)", {'x': 1234.56})
with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %x requires an integer, not str'):
eval("'%x' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %f requires a real number, not str'):
eval("'%f' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
Expand Down
22 changes: 11 additions & 11 deletions Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -1578,17 +1578,17 @@ def __int__(self):
self.assertEqual('%X' % letter_m, '6D')
self.assertEqual('%o' % letter_m, '155')
self.assertEqual('%c' % letter_m, 'm')
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not float', operator.mod, '%x', 3.14)
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not float', operator.mod, '%X', 2.11)
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not float', operator.mod, '%o', 1.79)
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not PseudoFloat', operator.mod, '%x', pi)
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 3j)
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 2j)
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1j)
self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j)
self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j)
self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j)
self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)
self.assertRaisesRegex(TypeError, '%x requires an integer, not float', operator.mod, '%x', 3.14)
self.assertRaisesRegex(TypeError, '%X requires an integer, not float', operator.mod, '%X', 2.11)
self.assertRaisesRegex(TypeError, '%o requires an integer, not float', operator.mod, '%o', 1.79)
self.assertRaisesRegex(TypeError, r'%x requires an integer, not .*\.PseudoFloat', operator.mod, '%x', pi)
self.assertRaisesRegex(TypeError, '%x requires an integer, not complex', operator.mod, '%x', 3j)
self.assertRaisesRegex(TypeError, '%X requires an integer, not complex', operator.mod, '%X', 2j)
self.assertRaisesRegex(TypeError, '%o requires an integer, not complex', operator.mod, '%o', 1j)
self.assertRaisesRegex(TypeError, '%u requires a real number, not complex', operator.mod, '%u', 3j)
self.assertRaisesRegex(TypeError, '%i requires a real number, not complex', operator.mod, '%i', 2j)
self.assertRaisesRegex(TypeError, '%d requires a real number, not complex', operator.mod, '%d', 1j)
self.assertRaisesRegex(TypeError, r'%c requires an integer or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)

class RaisingNumber:
def __int__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Improve error messages for printf-style formatting.
For errors in the format string, always include the position of the
start of the format unit.
For errors related to the formatted arguments, always include the number
or the name of the argument.
Raise more specific errors and include more information (type and number
of arguments, most probable causes of error).
Loading
Loading