Skip to content

Commit 012c498

Browse files
pythongh-142037: Improve error messages for printf-style formatting (pythonGH-142081)
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 formatted 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 when * is used with a mapping. * Add tests for some uncovered cases.
1 parent 5f736a0 commit 012c498

File tree

7 files changed

+591
-207
lines changed

7 files changed

+591
-207
lines changed

Lib/test/test_bytes.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -792,16 +792,16 @@ def __int__(self):
792792
pi = PseudoFloat(3.1415)
793793

794794
exceptions_params = [
795-
('%x format: an integer is required, not float', b'%x', 3.14),
796-
('%X format: an integer is required, not float', b'%X', 2.11),
797-
('%o format: an integer is required, not float', b'%o', 1.79),
798-
('%x format: an integer is required, not PseudoFloat', b'%x', pi),
799-
('%x format: an integer is required, not complex', b'%x', 3j),
800-
('%X format: an integer is required, not complex', b'%X', 2j),
801-
('%o format: an integer is required, not complex', b'%o', 1j),
802-
('%u format: a real number is required, not complex', b'%u', 3j),
803-
('%i format: a real number is required, not complex', b'%i', 2j),
804-
('%d format: a real number is required, not complex', b'%d', 2j),
795+
('%x requires an integer, not float', b'%x', 3.14),
796+
('%X requires an integer, not float', b'%X', 2.11),
797+
('%o requires an integer, not float', b'%o', 1.79),
798+
(r'%x requires an integer, not .*\.PseudoFloat', b'%x', pi),
799+
('%x requires an integer, not complex', b'%x', 3j),
800+
('%X requires an integer, not complex', b'%X', 2j),
801+
('%o requires an integer, not complex', b'%o', 1j),
802+
('%u requires a real number, not complex', b'%u', 3j),
803+
('%i requires a real number, not complex', b'%i', 2j),
804+
('%d requires a real number, not complex', b'%d', 2j),
805805
(
806806
r'%c requires an integer in range\(256\)'
807807
r' or a single byte, not .*\.PseudoFloat',
@@ -810,7 +810,7 @@ def __int__(self):
810810
]
811811

812812
for msg, format_bytes, value in exceptions_params:
813-
with self.assertRaisesRegex(TypeError, msg):
813+
with self.assertRaisesRegex(TypeError, 'format argument: ' + msg):
814814
operator.mod(format_bytes, value)
815815

816816
def test_memory_leak_gh_140939(self):

Lib/test/test_format.py

Lines changed: 197 additions & 42 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: %d requires a real number, 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: %x requires an integer, 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: %x requires an integer, 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: %f requires a real number, 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: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,17 +1578,40 @@ 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+
with self.assertRaisesRegex(TypeError,
1582+
'format argument: %x requires an integer, not float'):
1583+
'%x' % 3.14
1584+
with self.assertRaisesRegex(TypeError,
1585+
'format argument: %X requires an integer, not float'):
1586+
'%X' % 2.11
1587+
with self.assertRaisesRegex(TypeError,
1588+
'format argument: %o requires an integer, not float'):
1589+
'%o' % 1.79
1590+
with self.assertRaisesRegex(TypeError,
1591+
r'format argument: %x requires an integer, not .*\.PseudoFloat'):
1592+
'%x' % pi
1593+
with self.assertRaisesRegex(TypeError,
1594+
'format argument: %x requires an integer, not complex'):
1595+
'%x' % 3j
1596+
with self.assertRaisesRegex(TypeError,
1597+
'format argument: %X requires an integer, not complex'):
1598+
'%X' % 2j
1599+
with self.assertRaisesRegex(TypeError,
1600+
'format argument: %o requires an integer, not complex'):
1601+
'%o' % 1j
1602+
with self.assertRaisesRegex(TypeError,
1603+
'format argument: %u requires a real number, not complex'):
1604+
'%u' % 3j
1605+
with self.assertRaisesRegex(TypeError,
1606+
'format argument: %i requires a real number, not complex'):
1607+
'%i' % 2j
1608+
with self.assertRaisesRegex(TypeError,
1609+
'format argument: %d requires a real number, not complex'):
1610+
'%d' % 1j
1611+
with self.assertRaisesRegex(TypeError,
1612+
r'format argument: %c requires an integer or a unicode character, '
1613+
r'not .*\.PseudoFloat'):
1614+
'%c' % pi
15921615

15931616
class RaisingNumber:
15941617
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)