Skip to content

Commit c5b1321

Browse files
gh-142037: Improve error messages for printf-style formatting
This affects string formatting as well as bytes and bytearray 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. * Suggest more probable causes of errors in the format string (stray %, unsupported format, unexpected character). * Provide more information when the number of arguments does not match the number of format units. * Raise more specific errors when access of arguments by name is mixed with sequential access and if * is used with a mapping. * Add tests for some uncovered cases.
1 parent d2d2e92 commit c5b1321

File tree

7 files changed

+519
-186
lines changed

7 files changed

+519
-186
lines changed

Lib/test/test_bytes.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -781,16 +781,16 @@ def __int__(self):
781781
pi = PseudoFloat(3.1415)
782782

783783
exceptions_params = [
784-
('%x format: an integer is required, not float', b'%x', 3.14),
785-
('%X format: an integer is required, not float', b'%X', 2.11),
786-
('%o format: an integer is required, not float', b'%o', 1.79),
787-
('%x format: an integer is required, not PseudoFloat', b'%x', pi),
788-
('%x format: an integer is required, not complex', b'%x', 3j),
789-
('%X format: an integer is required, not complex', b'%X', 2j),
790-
('%o format: an integer is required, not complex', b'%o', 1j),
791-
('%u format: a real number is required, not complex', b'%u', 3j),
792-
('%i format: a real number is required, not complex', b'%i', 2j),
793-
('%d format: a real number is required, not complex', b'%d', 2j),
784+
('an integer is required for format %x, not float', b'%x', 3.14),
785+
('an integer is required for format %X, not float', b'%X', 2.11),
786+
('an integer is required for format %o, not float', b'%o', 1.79),
787+
(r'an integer is required for format %x, not .*\.PseudoFloat', b'%x', pi),
788+
('an integer is required for format %x, not complex', b'%x', 3j),
789+
('an integer is required for format %X, not complex', b'%X', 2j),
790+
('an integer is required for format %o, not complex', b'%o', 1j),
791+
('a real number is required for format %u, not complex', b'%u', 3j),
792+
('a real number is required for format %i, not complex', b'%i', 2j),
793+
('a real number is required for format %d, not complex', b'%d', 2j),
794794
(
795795
r'%c requires an integer in range\(256\)'
796796
r' or a single byte, not .*\.PseudoFloat',

Lib/test/test_format.py

Lines changed: 185 additions & 37 deletions
Large diffs are not rendered by default.

Lib/test/test_peepholer.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -733,22 +733,27 @@ def test_format_errors(self):
733733
with self.assertRaisesRegex(TypeError,
734734
'not all arguments converted during string formatting'):
735735
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
736-
with self.assertRaisesRegex(ValueError, 'incomplete format'):
736+
with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
737737
eval("'%s%' % (x,)", {'x': 1234})
738-
with self.assertRaisesRegex(ValueError, 'incomplete format'):
738+
with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
739739
eval("'%s%%%' % (x,)", {'x': 1234})
740740
with self.assertRaisesRegex(TypeError,
741741
'not enough arguments for format string'):
742742
eval("'%s%z' % (x,)", {'x': 1234})
743-
with self.assertRaisesRegex(ValueError, 'unsupported format character'):
743+
with self.assertRaisesRegex(ValueError,
744+
'unsupported format %z at position 2'):
744745
eval("'%s%z' % (x, 5)", {'x': 1234})
745-
with self.assertRaisesRegex(TypeError, 'a real number is required, not str'):
746+
with self.assertRaisesRegex(TypeError,
747+
'format argument 1: a real number is required for format %d, not str'):
746748
eval("'%d' % (x,)", {'x': '1234'})
747-
with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
749+
with self.assertRaisesRegex(TypeError,
750+
'format argument 1: an integer is required for format %x, not float'):
748751
eval("'%x' % (x,)", {'x': 1234.56})
749-
with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
752+
with self.assertRaisesRegex(TypeError,
753+
'format argument 1: an integer is required for format %x, not str'):
750754
eval("'%x' % (x,)", {'x': '1234'})
751-
with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
755+
with self.assertRaisesRegex(TypeError,
756+
'format argument 1: a real number is required for format %f, not str'):
752757
eval("'%f' % (x,)", {'x': '1234'})
753758
with self.assertRaisesRegex(TypeError,
754759
'not enough arguments for format string'):

Lib/test/test_str.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,17 +1578,17 @@ def __int__(self):
15781578
self.assertEqual('%X' % letter_m, '6D')
15791579
self.assertEqual('%o' % letter_m, '155')
15801580
self.assertEqual('%c' % letter_m, 'm')
1581-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not float', operator.mod, '%x', 3.14)
1582-
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not float', operator.mod, '%X', 2.11)
1583-
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not float', operator.mod, '%o', 1.79)
1584-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not PseudoFloat', operator.mod, '%x', pi)
1585-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 3j)
1586-
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 2j)
1587-
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1j)
1588-
self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j)
1589-
self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j)
1590-
self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j)
1591-
self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)
1581+
self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not float', operator.mod, '%x', 3.14)
1582+
self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not float', operator.mod, '%X', 2.11)
1583+
self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not float', operator.mod, '%o', 1.79)
1584+
self.assertRaisesRegex(TypeError, r'an integer is required for format %x, not .*\.PseudoFloat', operator.mod, '%x', pi)
1585+
self.assertRaisesRegex(TypeError, 'an integer is required for format %x, not complex', operator.mod, '%x', 3j)
1586+
self.assertRaisesRegex(TypeError, 'an integer is required for format %X, not complex', operator.mod, '%X', 2j)
1587+
self.assertRaisesRegex(TypeError, 'an integer is required for format %o, not complex', operator.mod, '%o', 1j)
1588+
self.assertRaisesRegex(TypeError, 'a real number is required for format %u, not complex', operator.mod, '%u', 3j)
1589+
self.assertRaisesRegex(TypeError, 'a real number is required for format %i, not complex', operator.mod, '%i', 2j)
1590+
self.assertRaisesRegex(TypeError, 'a real number is required for format %d, not complex', operator.mod, '%d', 1j)
1591+
self.assertRaisesRegex(TypeError, r'%c requires an integer or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)
15921592

15931593
class RaisingNumber:
15941594
def __int__(self):
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Improve error messages for printf-style formatting.
2+
For errors in the format string, always include the position of the
3+
start of the format unit.
4+
For errors related to the formatted arguments, always include the number
5+
or the name of the argument.
6+
Raise more specific errors and include more information (type and number
7+
of arguments, most probable causes of error).

0 commit comments

Comments
 (0)