https://github.com/python/cpython/commit/012c498035a9adfe9fd218907db1550ecb057f77
commit: 012c498035a9adfe9fd218907db1550ecb057f77
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-24T11:13:50Z
summary:

gh-142037: Improve error messages for printf-style formatting (GH-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.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
M Lib/test/test_bytes.py
M Lib/test/test_format.py
M Lib/test/test_peepholer.py
M Lib/test/test_str.py
M Objects/bytesobject.c
M Objects/unicode_format.c

diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index c42c0d4f5e9bc2..742bad21a3346b 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -792,16 +792,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),
+            ('%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',
@@ -810,7 +810,7 @@ def __int__(self):
         ]
 
         for msg, format_bytes, value in exceptions_params:
-            with self.assertRaisesRegex(TypeError, msg):
+            with self.assertRaisesRegex(TypeError, 'format argument: ' + msg):
                 operator.mod(format_bytes, value)
 
     def test_memory_leak_gh_140939(self):
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index 1f626d87fa6c7a..00f1ab44b0a8fa 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -57,10 +57,6 @@ def testcommon(formatstr, args, output=None, limit=None, 
overflowok=False):
     else:
         b_format = formatstr
     ba_format = bytearray(b_format)
-    b_args = []
-    if not isinstance(args, tuple):
-        args = (args, )
-    b_args = tuple(args)
     if output is None:
         b_output = ba_output = None
     else:
@@ -69,8 +65,8 @@ def testcommon(formatstr, args, output=None, limit=None, 
overflowok=False):
         else:
             b_output = output
         ba_output = bytearray(b_output)
-    testformat(b_format, b_args, b_output, limit, overflowok)
-    testformat(ba_format, b_args, ba_output, limit, overflowok)
+    testformat(b_format, args, b_output, limit, overflowok)
+    testformat(ba_format, args, ba_output, limit, overflowok)
 
 def test_exc(formatstr, args, exception, excmsg):
     try:
@@ -82,6 +78,7 @@ def test_exc(formatstr, args, exception, excmsg):
         else:
             if verbose: print('no')
             print('Unexpected ', exception, ':', repr(str(exc)))
+            raise
     except:
         if verbose: print('no')
         print('Unexpected exception')
@@ -92,6 +89,8 @@ def test_exc(formatstr, args, exception, excmsg):
 def test_exc_common(formatstr, args, exception, excmsg):
     # test str and bytes
     test_exc(formatstr, args, exception, excmsg)
+    if isinstance(args, dict):
+        args = {k.encode('ascii'): v for k, v in args.items()}
     test_exc(formatstr.encode('ascii'), args, exception, excmsg)
 
 class FormatTest(unittest.TestCase):
@@ -272,45 +271,154 @@ def test_common_format(self):
 
         if verbose:
             print('Testing exceptions')
-        test_exc_common('%', (), ValueError, "incomplete format")
-        test_exc_common('% %s', 1, ValueError,
-                        "unsupported format character '%' (0x25) at index 2")
+        test_exc_common('abc %', (), ValueError, "stray % at position 4")
+        test_exc_common('abc % %s', 1, ValueError,
+                        "stray % at position 4 or unexpected format character 
'%' at position 6")
+        test_exc_common('abc %z', 1, ValueError,
+                        "unsupported format %z at position 4")
+        test_exc_common("abc %Id", 1, ValueError,
+                        "unsupported format %I at position 4")
+        test_exc_common("abc %'d", 1, ValueError,
+                        "stray % at position 4 or unexpected format character 
\"'\" at position 5")
+        test_exc_common("abc %1 d", 1, ValueError,
+                        "stray % at position 4 or unexpected format character 
' ' at position 6")
+        test_exc_common('abc % (x)r', {}, ValueError,
+                        "stray % at position 4 or unexpected format character 
'(' at position 6")
+        test_exc_common('abc %((x)r', {}, ValueError,
+                        "stray % or incomplete format key at position 4")
+        test_exc_common('%r %r', 1, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%r %r', (1,), TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%r', (), TypeError,
+                        "not enough arguments for format string (got 0)")
+        test_exc_common('abc %' + '9'*50 + 'r', 1, ValueError,
+                        "width too big at position 4")
+        test_exc_common('abc %.' + '9'*50 + 'r', 1, ValueError,
+                        "precision too big at position 4")
+        test_exc_common('%r %*r', 1, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%r %*r', (1,), TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%*r', (1,), TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%*r', (), TypeError,
+                        "not enough arguments for format string (got 0)")
+        test_exc_common('%r %.*r', 1, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%r %.*r', (1,), TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%.*r', (1,), TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%.*r', (), TypeError,
+                        "not enough arguments for format string (got 0)")
+        test_exc_common('%(x)r', 1, TypeError,
+                        "format requires a mapping, not int")
+        test_exc_common('%*r', 1, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%*r', 3.14, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%*r', (3.14, 1), TypeError,
+                        "format argument 1: * requires int, not float")
+        test_exc_common('%.*r', 1, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%.*r', 3.14, TypeError,
+                        "not enough arguments for format string (got 1)")
+        test_exc_common('%.*r', (3.14, 1), TypeError,
+                        "format argument 1: * requires int, not float")
+        test_exc_common('%*r', (2**1000, 1), OverflowError,
+                        "format argument 1: too big for width")
+        test_exc_common('%*r', (-2**1000, 1), OverflowError,
+                        "format argument 1: too big for width")
+        test_exc_common('%.*r', (2**1000, 1), OverflowError,
+                        "format argument 1: too big for precision")
+        test_exc_common('%.*r', (-2**1000, 1), OverflowError,
+                        "format argument 1: too big for precision")
         test_exc_common('%d', '1', TypeError,
-                        "%d format: a real number is required, not str")
+                        "format argument: %d requires a real number, not str")
         test_exc_common('%d', b'1', TypeError,
-                        "%d format: a real number is required, not bytes")
+                        "format argument: %d requires a real number, not 
bytes")
+        test_exc_common('%d', ('1',), TypeError,
+                        "format argument 1: %d requires a real number, not 
str")
         test_exc_common('%x', '1', TypeError,
-                        "%x format: an integer is required, not str")
+                        "format argument: %x requires an integer, not str")
         test_exc_common('%x', 3.14, TypeError,
-                        "%x format: an integer is required, not float")
+                        "format argument: %x requires an integer, not float")
+        test_exc_common('%x', ('1',), TypeError,
+                        "format argument 1: %x requires an integer, not str")
         test_exc_common('%i', '1', TypeError,
-                        "%i format: a real number is required, not str")
+                        "format argument: %i requires a real number, not str")
         test_exc_common('%i', b'1', TypeError,
-                        "%i format: a real number is required, not bytes")
+                        "format argument: %i requires a real number, not 
bytes")
+        test_exc_common('%g', '1', TypeError,
+                        "format argument: %g requires a real number, not str")
+        test_exc_common('%g', ('1',), TypeError,
+                        "format argument 1: %g requires a real number, not 
str")
 
     def test_str_format(self):
         testformat("%r", "\u0378", "'\\u0378'")  # non printable
         testformat("%a", "\u0378", "'\\u0378'")  # non printable
         testformat("%r", "\u0374", "'\u0374'")   # printable
         testformat("%a", "\u0374", "'\\u0374'")  # printable
+        testformat('%(x)r', {'x': 1}, '1')
 
         # Test exception for unknown format characters, etc.
         if verbose:
             print('Testing exceptions')
         test_exc('abc %b', 1, ValueError,
-                 "unsupported format character 'b' (0x62) at index 5")
-        #test_exc(unicode('abc %\u3000','raw-unicode-escape'), 1, ValueError,
-        #         "unsupported format character '?' (0x3000) at index 5")
-        test_exc('%g', '1', TypeError, "must be real number, not str")
+                 "unsupported format %b at position 4")
+        test_exc("abc %\nd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character U+000A 
at position 5")
+        test_exc("abc %\x1fd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character U+001F 
at position 5")
+        test_exc("abc %\x7fd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character U+007F 
at position 5")
+        test_exc("abc %\x80d", 1, ValueError,
+                 "stray % at position 4 or unexpected format character U+0080 
at position 5")
+        test_exc('abc %äd', 1, ValueError,
+                 "stray % at position 4 or unexpected format character 'ä' 
(U+00E4) at position 5")
+        test_exc('abc %€d', 1, ValueError,
+                 "stray % at position 4 or unexpected format character '€' 
(U+20AC) at position 5")
         test_exc('no format', '1', TypeError,
-                 "not all arguments converted during string formatting")
-        test_exc('%c', -1, OverflowError, "%c arg not in range(0x110000)")
+                 "not all arguments converted during string formatting 
(required 0, got 1)")
+        test_exc('%r', (1, 2), TypeError,
+                 "not all arguments converted during string formatting 
(required 1, got 2)")
+        test_exc('%(x)r %r', {'x': 1}, ValueError,
+                 "format requires a parenthesised mapping key at position 6")
+        test_exc('%(x)*r', {'x': 1}, ValueError,
+                 "* cannot be used with a parenthesised mapping key at 
position 0")
+        test_exc('%(x).*r', {'x': 1}, ValueError,
+                 "* cannot be used with a parenthesised mapping key at 
position 0")
+        test_exc('%(x)d', {'x': '1'}, TypeError,
+                 "format argument 'x': %d requires a real number, not str")
+        test_exc('%(x)x', {'x': '1'}, TypeError,
+                 "format argument 'x': %x requires an integer, not str")
+        test_exc('%(x)g', {'x': '1'}, TypeError,
+                 "format argument 'x': %g requires a real number, not str")
+        test_exc('%c', -1, OverflowError,
+                 "format argument: %c argument not in range(0x110000)")
+        test_exc('%c', (-1,), OverflowError,
+                 "format argument 1: %c argument not in range(0x110000)")
+        test_exc('%(x)c', {'x': -1}, OverflowError,
+                 "format argument 'x': %c argument not in range(0x110000)")
         test_exc('%c', sys.maxunicode+1, OverflowError,
-                 "%c arg not in range(0x110000)")
-        #test_exc('%c', 2**128, OverflowError, "%c arg not in range(0x110000)")
-        test_exc('%c', 3.14, TypeError, "%c requires an int or a unicode 
character, not float")
-        test_exc('%c', 'ab', TypeError, "%c requires an int or a unicode 
character, not a string of length 2")
-        test_exc('%c', b'x', TypeError, "%c requires an int or a unicode 
character, not bytes")
+                 "format argument: %c argument not in range(0x110000)")
+        test_exc('%c', 2**128, OverflowError,
+                 "format argument: %c argument not in range(0x110000)")
+        test_exc('%c', 3.14, TypeError,
+                 "format argument: %c requires an integer or a unicode 
character, not float")
+        test_exc('%c', (3.14,), TypeError,
+                 "format argument 1: %c requires an integer or a unicode 
character, not float")
+        test_exc('%(x)c', {'x': 3.14}, TypeError,
+                 "format argument 'x': %c requires an integer or a unicode 
character, not float")
+        test_exc('%c', 'ab', TypeError,
+                 "format argument: %c requires an integer or a unicode 
character, not a string of length 2")
+        test_exc('%c', ('ab',), TypeError,
+                 "format argument 1: %c requires an integer or a unicode 
character, not a string of length 2")
+        test_exc('%(x)c', {'x': 'ab'}, TypeError,
+                 "format argument 'x': %c requires an integer or a unicode 
character, not a string of length 2")
+        test_exc('%c', b'x', TypeError,
+                 "format argument: %c requires an integer or a unicode 
character, not bytes")
 
         if maxsize == 2**31-1:
             # crashes 2.2.1 and earlier:
@@ -355,36 +463,83 @@ def __bytes__(self):
         testcommon(b"%r", b"ghi", b"b'ghi'")
         testcommon(b"%r", "jkl", b"'jkl'")
         testcommon(b"%r", "\u0544", b"'\\u0544'")
+        testcommon(b'%(x)r', {b'x': 1}, b'1')
 
         # Test exception for unknown format characters, etc.
         if verbose:
             print('Testing exceptions')
-        test_exc(b'%g', '1', TypeError, "float argument required, not str")
-        test_exc(b'%g', b'1', TypeError, "float argument required, not bytes")
+        test_exc(b"abc %\nd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character with 
code 0x0a at position 5")
+        test_exc(b"abc %'d", 1, ValueError,
+                 "stray % at position 4 or unexpected format character \"'\" 
at position 5")
+        test_exc(b"abc %\x1fd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character with 
code 0x1f at position 5")
+        test_exc(b"abc %\x7fd", 1, ValueError,
+                 "stray % at position 4 or unexpected format character with 
code 0x7f at position 5")
+        test_exc(b"abc %\x80d", 1, ValueError,
+                 "stray % at position 4 or unexpected format character with 
code 0x80 at position 5")
         test_exc(b'no format', 7, TypeError,
-                 "not all arguments converted during bytes formatting")
+                 "not all arguments converted during bytes formatting 
(required 0, got 1)")
         test_exc(b'no format', b'1', TypeError,
-                 "not all arguments converted during bytes formatting")
+                 "not all arguments converted during bytes formatting 
(required 0, got 1)")
         test_exc(b'no format', bytearray(b'1'), TypeError,
-                 "not all arguments converted during bytes formatting")
+                 "not all arguments converted during bytes formatting 
(required 0, got 1)")
+        test_exc(b'%r', (1, 2), TypeError,
+                 "not all arguments converted during bytes formatting 
(required 1, got 2)")
+        test_exc(b'%(x)r %r', {b'x': 1}, ValueError,
+                 "format requires a parenthesised mapping key at position 6")
+        test_exc(b'%(x)*r', {b'x': 1}, ValueError,
+                 "* cannot be used with a parenthesised mapping key at 
position 0")
+        test_exc(b'%(x).*r', {b'x': 1}, ValueError,
+                 "* cannot be used with a parenthesised mapping key at 
position 0")
+        test_exc(b'%(x)d', {b'x': '1'}, TypeError,
+                 "format argument b'x': %d requires a real number, not str")
+        test_exc(b'%(x)x', {b'x': '1'}, TypeError,
+                 "format argument b'x': %x requires an integer, not str")
+        test_exc(b'%(x)g', {b'x': '1'}, TypeError,
+                 "format argument b'x': %g requires a real number, not str")
         test_exc(b"%c", -1, OverflowError,
-                "%c arg not in range(256)")
+                "format argument: %c argument not in range(256)")
+        test_exc(b"%c", (-1,), OverflowError,
+                "format argument 1: %c argument not in range(256)")
+        test_exc(b"%(x)c", {b'x': -1}, OverflowError,
+                "format argument b'x': %c argument not in range(256)")
         test_exc(b"%c", 256, OverflowError,
-                "%c arg not in range(256)")
+                "format argument: %c argument not in range(256)")
         test_exc(b"%c", 2**128, OverflowError,
-                "%c arg not in range(256)")
+                "format argument: %c argument not in range(256)")
         test_exc(b"%c", b"Za", TypeError,
-                "%c requires an integer in range(256) or a single byte, not a 
bytes object of length 2")
+                "format argument: %c requires an integer in range(256) or a 
single byte, not a bytes object of length 2")
+        test_exc(b"%c", (b"Za",), TypeError,
+                "format argument 1: %c requires an integer in range(256) or a 
single byte, not a bytes object of length 2")
+        test_exc(b"%(x)c", {b'x': b"Za"}, TypeError,
+                "format argument b'x': %c requires an integer in range(256) or 
a single byte, not a bytes object of length 2")
+        test_exc(b"%c", bytearray(b"Za"), TypeError,
+                "format argument: %c requires an integer in range(256) or a 
single byte, not a bytearray object of length 2")
+        test_exc(b"%c", (bytearray(b"Za"),), TypeError,
+                "format argument 1: %c requires an integer in range(256) or a 
single byte, not a bytearray object of length 2")
+        test_exc(b"%(x)c", {b'x': bytearray(b"Za")}, TypeError,
+                "format argument b'x': %c requires an integer in range(256) or 
a single byte, not a bytearray object of length 2")
         test_exc(b"%c", "Y", TypeError,
-                "%c requires an integer in range(256) or a single byte, not 
str")
+                "format argument: %c requires an integer in range(256) or a 
single byte, not str")
         test_exc(b"%c", 3.14, TypeError,
-                "%c requires an integer in range(256) or a single byte, not 
float")
+                "format argument: %c requires an integer in range(256) or a 
single byte, not float")
+        test_exc(b"%c", (3.14,), TypeError,
+                "format argument 1: %c requires an integer in range(256) or a 
single byte, not float")
+        test_exc(b"%(x)c", {b'x': 3.14}, TypeError,
+                "format argument b'x': %c requires an integer in range(256) or 
a single byte, not float")
         test_exc(b"%b", "Xc", TypeError,
-                "%b requires a bytes-like object, "
-                 "or an object that implements __bytes__, not 'str'")
+                "format argument: %b requires a bytes-like object, "
+                 "or an object that implements __bytes__, not str")
+        test_exc(b"%b", ("Xc",), TypeError,
+                "format argument 1: %b requires a bytes-like object, "
+                 "or an object that implements __bytes__, not str")
+        test_exc(b"%(x)b", {b'x': "Xc"}, TypeError,
+                "format argument b'x': %b requires a bytes-like object, "
+                 "or an object that implements __bytes__, not str")
         test_exc(b"%s", "Wd", TypeError,
-                "%b requires a bytes-like object, "
-                 "or an object that implements __bytes__, not 'str'")
+                "format argument: %b requires a bytes-like object, "
+                 "or an object that implements __bytes__, not str")
 
         if maxsize == 2**31-1:
             # crashes 2.2.1 and earlier:
@@ -626,7 +781,7 @@ def test_specifier_z_error(self):
         with self.assertRaisesRegex(ValueError, error_msg):
             f"{'x':zs}"  # can't apply to string
 
-        error_msg = re.escape("unsupported format character 'z'")
+        error_msg = re.escape("unsupported format %z at position 0")
         with self.assertRaisesRegex(ValueError, error_msg):
             "%z.1f" % 0  # not allowed in old style string interpolation
         with self.assertRaisesRegex(ValueError, error_msg):
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 1caf6de4a14e39..88d20bbb028d6f 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -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'):
             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'):
diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py
index 2584fbf72d3fa6..0a8dddb026f6c8 100644
--- a/Lib/test/test_str.py
+++ b/Lib/test/test_str.py
@@ -1578,17 +1578,40 @@ 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)
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %x requires an integer, not float'):
+            '%x' % 3.14
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %X requires an integer, not float'):
+            '%X' % 2.11
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %o requires an integer, not float'):
+            '%o' % 1.79
+        with self.assertRaisesRegex(TypeError,
+                r'format argument: %x requires an integer, not 
.*\.PseudoFloat'):
+            '%x' % pi
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %x requires an integer, not complex'):
+            '%x' % 3j
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %X requires an integer, not complex'):
+            '%X' % 2j
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %o requires an integer, not complex'):
+            '%o' % 1j
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %u requires a real number, not complex'):
+            '%u' % 3j
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %i requires a real number, not complex'):
+            '%i' % 2j
+        with self.assertRaisesRegex(TypeError,
+                'format argument: %d requires a real number, not complex'):
+            '%d' % 1j
+        with self.assertRaisesRegex(TypeError,
+                r'format argument: %c requires an integer or a unicode 
character, '
+                r'not .*\.PseudoFloat'):
+            '%c' % pi
 
         class RaisingNumber:
             def __int__(self):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
new file mode 100644
index 00000000000000..6a59be7726f254
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
@@ -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).
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 56de99bde11682..b85cbfe43e11e0 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -404,26 +404,44 @@ PyBytes_FromFormat(const char *format, ...)
 
 /* Helpers for formatstring */
 
+#define FORMAT_ERROR(EXC, FMT, ...) do {                                    \
+    if (key != NULL) {                                                      \
+        PyErr_Format((EXC), "format argument %R: " FMT,                     \
+                     key, __VA_ARGS__);                                     \
+    }                                                                       \
+    else if (argidx >= 0) {                                                 \
+        PyErr_Format((EXC), "format argument %zd: " FMT,                    \
+                     argidx, __VA_ARGS__);                                  \
+    }                                                                       \
+    else {                                                                  \
+        PyErr_Format((EXC), "format argument: " FMT, __VA_ARGS__);          \
+    }                                                                       \
+} while (0)
+
 Py_LOCAL_INLINE(PyObject *)
-getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx)
+getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx, int 
allowone)
 {
     Py_ssize_t argidx = *p_argidx;
     if (argidx < arglen) {
         (*p_argidx)++;
-        if (arglen < 0)
-            return args;
-        else
+        if (arglen >= 0) {
             return PyTuple_GetItem(args, argidx);
+        }
+        else if (allowone) {
+            return args;
+        }
     }
-    PyErr_SetString(PyExc_TypeError,
-                    "not enough arguments for format string");
+    PyErr_Format(PyExc_TypeError,
+                 "not enough arguments for format string (got %zd)",
+                 arglen < 0 ? 1 : arglen);
     return NULL;
 }
 
 /* Returns a new reference to a PyBytes object, or NULL on failure. */
 
 static char*
-formatfloat(PyObject *v, int flags, int prec, int type,
+formatfloat(PyObject *v, Py_ssize_t argidx, PyObject *key,
+            int flags, int prec, int type,
             PyObject **p_result, PyBytesWriter *writer, char *str)
 {
     char *p;
@@ -434,8 +452,11 @@ formatfloat(PyObject *v, int flags, int prec, int type,
 
     x = PyFloat_AsDouble(v);
     if (x == -1.0 && PyErr_Occurred()) {
-        PyErr_Format(PyExc_TypeError, "float argument required, "
-                     "not %.200s", Py_TYPE(v)->tp_name);
+        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+            FORMAT_ERROR(PyExc_TypeError,
+                         "%%%c requires a real number, not %T",
+                         type, v);
+        }
         return NULL;
     }
 
@@ -470,7 +491,8 @@ formatfloat(PyObject *v, int flags, int prec, int type,
 }
 
 static PyObject *
-formatlong(PyObject *v, int flags, int prec, int type)
+formatlong(PyObject *v, Py_ssize_t argidx, PyObject *key,
+           int flags, int prec, int type)
 {
     PyObject *result, *iobj;
     if (PyLong_Check(v))
@@ -490,20 +512,21 @@ formatlong(PyObject *v, int flags, int prec, int type)
         if (!PyErr_ExceptionMatches(PyExc_TypeError))
             return NULL;
     }
-    PyErr_Format(PyExc_TypeError,
-        "%%%c format: %s is required, not %.200s", type,
-        (type == 'o' || type == 'x' || type == 'X') ? "an integer"
-                                                    : "a real number",
-        Py_TYPE(v)->tp_name);
+    FORMAT_ERROR(PyExc_TypeError,
+                 "%%%c requires %s, not %T",
+                 type,
+                 (type == 'o' || type == 'x' || type == 'X') ? "an integer"
+                                                             : "a real number",
+                 v);
     return NULL;
 }
 
 static int
-byte_converter(PyObject *arg, char *p)
+byte_converter(PyObject *arg, Py_ssize_t argidx, PyObject *key, char *p)
 {
     if (PyBytes_Check(arg)) {
         if (PyBytes_GET_SIZE(arg) != 1) {
-            PyErr_Format(PyExc_TypeError,
+            FORMAT_ERROR(PyExc_TypeError,
                          "%%c requires an integer in range(256) or "
                          "a single byte, not a bytes object of length %zd",
                          PyBytes_GET_SIZE(arg));
@@ -514,7 +537,7 @@ byte_converter(PyObject *arg, char *p)
     }
     else if (PyByteArray_Check(arg)) {
         if (PyByteArray_GET_SIZE(arg) != 1) {
-            PyErr_Format(PyExc_TypeError,
+            FORMAT_ERROR(PyExc_TypeError,
                          "%%c requires an integer in range(256) or "
                          "a single byte, not a bytearray object of length %zd",
                          PyByteArray_GET_SIZE(arg));
@@ -531,23 +554,25 @@ byte_converter(PyObject *arg, char *p)
         }
         if (!(0 <= ival && ival <= 255)) {
             /* this includes an overflow in converting to C long */
-            PyErr_SetString(PyExc_OverflowError,
-                            "%c arg not in range(256)");
+            FORMAT_ERROR(PyExc_OverflowError,
+                         "%%c argument not in range(256)%s", "");
             return 0;
         }
         *p = (char)ival;
         return 1;
     }
-    PyErr_Format(PyExc_TypeError,
-        "%%c requires an integer in range(256) or a single byte, not %T",
-        arg);
+    FORMAT_ERROR(PyExc_TypeError,
+                 "%%c requires an integer in range(256) or "
+                 "a single byte, not %T",
+                 arg);
     return 0;
 }
 
 static PyObject *_PyBytes_FromBuffer(PyObject *x);
 
 static PyObject *
-format_obj(PyObject *v, const char **pbuf, Py_ssize_t *plen)
+format_obj(PyObject *v, Py_ssize_t argidx, PyObject *key,
+           const char **pbuf, Py_ssize_t *plen)
 {
     PyObject *func, *result;
     /* is it a bytes object? */
@@ -589,10 +614,10 @@ format_obj(PyObject *v, const char **pbuf, Py_ssize_t 
*plen)
         *plen = PyBytes_GET_SIZE(result);
         return result;
     }
-    PyErr_Format(PyExc_TypeError,
+    FORMAT_ERROR(PyExc_TypeError,
                  "%%b requires a bytes-like object, "
-                 "or an object that implements __bytes__, not '%.100s'",
-                 Py_TYPE(v)->tp_name);
+                 "or an object that implements __bytes__, not %T",
+                 v);
     return NULL;
 }
 
@@ -607,6 +632,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
     Py_ssize_t fmtcnt;
     int args_owned = 0;
     PyObject *dict = NULL;
+    PyObject *key = NULL;
 
     if (args == NULL) {
         PyErr_BadInternalCall();
@@ -680,15 +706,17 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                 fmtcnt--;
                 continue;
             }
+            Py_CLEAR(key);
+            const char *fmtstart = fmt;
             if (*fmt == '(') {
                 const char *keystart;
                 Py_ssize_t keylen;
-                PyObject *key;
                 int pcount = 1;
 
                 if (dict == NULL) {
-                    PyErr_SetString(PyExc_TypeError,
-                             "format requires a mapping");
+                    PyErr_Format(PyExc_TypeError,
+                                 "format requires a mapping, not %T",
+                                 args);
                     goto error;
                 }
                 ++fmt;
@@ -704,8 +732,10 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                 }
                 keylen = fmt - keystart - 1;
                 if (fmtcnt < 0 || pcount > 0) {
-                    PyErr_SetString(PyExc_ValueError,
-                               "incomplete format key");
+                    PyErr_Format(PyExc_ValueError,
+                                 "stray %% or incomplete format key "
+                                 "at position %zd",
+                                 (Py_ssize_t)(fmtstart - format - 1));
                     goto error;
                 }
                 key = PyBytes_FromStringAndSize(keystart,
@@ -717,13 +747,21 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                     args_owned = 0;
                 }
                 args = PyObject_GetItem(dict, key);
-                Py_DECREF(key);
                 if (args == NULL) {
                     goto error;
                 }
                 args_owned = 1;
-                arglen = -1;
-                argidx = -2;
+                arglen = -3;
+                argidx = -4;
+            }
+            else {
+                if (arglen < -1) {
+                    PyErr_Format(PyExc_ValueError,
+                                 "format requires a parenthesised mapping key "
+                                 "at position %zd",
+                                 (Py_ssize_t)(fmtstart - format - 1));
+                    goto error;
+                }
             }
 
             /* Parse flags. Example: "%+i" => flags=F_SIGN. */
@@ -740,17 +778,28 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
 
             /* Parse width. Example: "%10s" => width=10 */
             if (c == '*') {
-                v = getnextarg(args, arglen, &argidx);
+                if (arglen < -1) {
+                    PyErr_Format(PyExc_ValueError,
+                            "* cannot be used with a parenthesised mapping key 
"
+                            "at position %zd",
+                            (Py_ssize_t)(fmtstart - format - 1));
+                    goto error;
+                }
+                v = getnextarg(args, arglen, &argidx, 0);
                 if (v == NULL)
                     goto error;
                 if (!PyLong_Check(v)) {
-                    PyErr_SetString(PyExc_TypeError,
-                                    "* wants int");
+                    FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
                     goto error;
                 }
                 width = PyLong_AsSsize_t(v);
-                if (width == -1 && PyErr_Occurred())
+                if (width == -1 && PyErr_Occurred()) {
+                    if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+                        FORMAT_ERROR(PyExc_OverflowError,
+                                     "too big for width%s", "");
+                    }
                     goto error;
+                }
                 if (width < 0) {
                     flags |= F_LJUST;
                     width = -width;
@@ -765,9 +814,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
                     if (!Py_ISDIGIT(c))
                         break;
                     if (width > (PY_SSIZE_T_MAX - ((int)c - '0')) / 10) {
-                        PyErr_SetString(
-                            PyExc_ValueError,
-                            "width too big");
+                        PyErr_Format(PyExc_ValueError,
+                                     "width too big at position %zd",
+                                     (Py_ssize_t)(fmtstart - format - 1));
                         goto error;
                     }
                     width = width*10 + (c - '0');
@@ -780,18 +829,29 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                 if (--fmtcnt >= 0)
                     c = *fmt++;
                 if (c == '*') {
-                    v = getnextarg(args, arglen, &argidx);
+                    if (arglen < -1) {
+                        PyErr_Format(PyExc_ValueError,
+                                "* cannot be used with a parenthesised mapping 
key "
+                                "at position %zd",
+                                (Py_ssize_t)(fmtstart - format - 1));
+                        goto error;
+                    }
+                    v = getnextarg(args, arglen, &argidx, 0);
                     if (v == NULL)
                         goto error;
                     if (!PyLong_Check(v)) {
-                        PyErr_SetString(
-                            PyExc_TypeError,
-                            "* wants int");
+                        FORMAT_ERROR(PyExc_TypeError,
+                                     "* requires int, not %T", v);
                         goto error;
                     }
                     prec = PyLong_AsInt(v);
-                    if (prec == -1 && PyErr_Occurred())
+                    if (prec == -1 && PyErr_Occurred()) {
+                        if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+                            FORMAT_ERROR(PyExc_OverflowError,
+                                         "too big for precision%s", "");
+                        }
                         goto error;
+                    }
                     if (prec < 0)
                         prec = 0;
                     if (--fmtcnt >= 0)
@@ -804,9 +864,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
                         if (!Py_ISDIGIT(c))
                             break;
                         if (prec > (INT_MAX - ((int)c - '0')) / 10) {
-                            PyErr_SetString(
-                                PyExc_ValueError,
-                                "prec too big");
+                            PyErr_Format(PyExc_ValueError,
+                                "precision too big at position %zd",
+                                (Py_ssize_t)(fmtstart - format - 1));
                             goto error;
                         }
                         prec = prec*10 + (c - '0');
@@ -820,11 +880,12 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                 }
             }
             if (fmtcnt < 0) {
-                PyErr_SetString(PyExc_ValueError,
-                                "incomplete format");
+                PyErr_Format(PyExc_ValueError,
+                             "stray %% at position %zd",
+                             (Py_ssize_t)(fmtstart - format - 1));
                 goto error;
             }
-            v = getnextarg(args, arglen, &argidx);
+            v = getnextarg(args, arglen, &argidx, 1);
             if (v == NULL)
                 goto error;
 
@@ -852,7 +913,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
             case 's':
                 // %s is only for 2/3 code; 3 only code should use %b
             case 'b':
-                temp = format_obj(v, &pbuf, &len);
+                temp = format_obj(v, argidx, key, &pbuf, &len);
                 if (temp == NULL)
                     goto error;
                 if (prec >= 0 && len > prec)
@@ -900,7 +961,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
                     continue;
                 }
 
-                temp = formatlong(v, flags, prec, c);
+                temp = formatlong(v, argidx, key, flags, prec, c);
                 if (!temp)
                     goto error;
                 assert(PyUnicode_IS_ASCII(temp));
@@ -921,13 +982,13 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                     && !(flags & (F_SIGN | F_BLANK)))
                 {
                     /* Fast path */
-                    res = formatfloat(v, flags, prec, c, NULL, writer, res);
+                    res = formatfloat(v, argidx, key, flags, prec, c, NULL, 
writer, res);
                     if (res == NULL)
                         goto error;
                     continue;
                 }
 
-                if (!formatfloat(v, flags, prec, c, &temp, NULL, res))
+                if (!formatfloat(v, argidx, key, flags, prec, c, &temp, NULL, 
res))
                     goto error;
                 pbuf = PyBytes_AS_STRING(temp);
                 len = PyBytes_GET_SIZE(temp);
@@ -938,7 +999,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
 
             case 'c':
                 pbuf = &onechar;
-                len = byte_converter(v, &onechar);
+                len = byte_converter(v, argidx, key, &onechar);
                 if (!len)
                     goto error;
                 if (width == -1) {
@@ -949,11 +1010,36 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
                 break;
 
             default:
-                PyErr_Format(PyExc_ValueError,
-                  "unsupported format character '%c' (0x%x) "
-                  "at index %zd",
-                  c, c,
-                  (Py_ssize_t)(fmt - 1 - format));
+                if (Py_ISALPHA(c)) {
+                    PyErr_Format(PyExc_ValueError,
+                                 "unsupported format %%%c at position %zd",
+                                 c, (Py_ssize_t)(fmtstart - format - 1));
+                }
+                else if (c == '\'') {
+                    PyErr_Format(PyExc_ValueError,
+                                 "stray %% at position %zd or unexpected "
+                                 "format character \"'\" "
+                                 "at position %zd",
+                                 (Py_ssize_t)(fmtstart - format - 1),
+                                 (Py_ssize_t)(fmt - format - 1));
+                }
+                else if (c >= 32 && c < 127 && c != '\'') {
+                    PyErr_Format(PyExc_ValueError,
+                                 "stray %% at position %zd or unexpected "
+                                 "format character '%c' "
+                                 "at position %zd",
+                                 (Py_ssize_t)(fmtstart - format - 1),
+                                 c, (Py_ssize_t)(fmt - format - 1));
+                }
+                else {
+                    PyErr_Format(PyExc_ValueError,
+                                 "stray %% at position %zd or unexpected "
+                                 "format character with code 0x%02x "
+                                 "at position %zd",
+                                 (Py_ssize_t)(fmtstart - format - 1),
+                                 Py_CHARMASK(c),
+                                 (Py_ssize_t)(fmt - format - 1));
+                }
                 goto error;
             }
 
@@ -1042,6 +1128,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
             }
 
             if (dict && (argidx < arglen)) {
+                // XXX: Never happens?
                 PyErr_SetString(PyExc_TypeError,
                            "not all arguments converted during bytes 
formatting");
                 Py_XDECREF(temp);
@@ -1061,8 +1148,11 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
     } /* until end */
 
     if (argidx < arglen && !dict) {
-        PyErr_SetString(PyExc_TypeError,
-                        "not all arguments converted during bytes formatting");
+        PyErr_Format(PyExc_TypeError,
+                     "not all arguments converted during bytes formatting "
+                     "(required %zd, got %zd)",
+                     arglen < 0 ? 0 : argidx,
+                     arglen < 0 ? 1 : arglen);
         goto error;
     }
 
@@ -1072,6 +1162,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t 
format_len,
     return PyBytesWriter_FinishWithPointer(writer, res);
 
  error:
+    Py_XDECREF(key);
     PyBytesWriter_Discard(writer);
     if (args_owned) {
         Py_DECREF(args);
diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c
index 26bdae55d8b931..e2790c8c1d4343 100644
--- a/Objects/unicode_format.c
+++ b/Objects/unicode_format.c
@@ -72,23 +72,44 @@ struct unicode_format_arg_t {
     Py_ssize_t width;
     int prec;
     int sign;
+    Py_ssize_t fmtstart;
+    PyObject *key;
 };
 
 
+// Use FORMAT_ERROR("...%s", "") when there is no arguments.
+#define FORMAT_ERROR(EXC, FMT, ...) do {                                    \
+    if (arg->key != NULL) {                                                 \
+        PyErr_Format((EXC), "format argument %R: " FMT,                     \
+                     arg->key, __VA_ARGS__);                                \
+    }                                                                       \
+    else if (ctx->argidx >= 0) {                                            \
+        PyErr_Format((EXC), "format argument %zd: " FMT,                    \
+                     ctx->argidx, __VA_ARGS__);                             \
+    }                                                                       \
+    else {                                                                  \
+        PyErr_Format((EXC), "format argument: " FMT, __VA_ARGS__);          \
+    }                                                                       \
+} while (0)
+
+
 static PyObject *
-unicode_format_getnextarg(struct unicode_formatter_t *ctx)
+unicode_format_getnextarg(struct unicode_formatter_t *ctx, int allowone)
 {
     Py_ssize_t argidx = ctx->argidx;
 
-    if (argidx < ctx->arglen) {
+    if (argidx < ctx->arglen && (allowone || ctx->arglen >= 0)) {
         ctx->argidx++;
-        if (ctx->arglen < 0)
-            return ctx->args;
-        else
+        if (ctx->arglen >= 0) {
             return PyTuple_GetItem(ctx->args, argidx);
+        }
+        else if (allowone) {
+            return ctx->args;
+        }
     }
-    PyErr_SetString(PyExc_TypeError,
-                    "not enough arguments for format string");
+    PyErr_Format(PyExc_TypeError,
+                 "not enough arguments for format string (got %zd)",
+                 ctx->arglen < 0 ? 1 : ctx->arglen);
     return NULL;
 }
 
@@ -100,7 +121,9 @@ unicode_format_getnextarg(struct unicode_formatter_t *ctx)
 
    Return 0 on success, raise an exception and return -1 on error. */
 static int
-formatfloat(PyObject *v, struct unicode_format_arg_t *arg,
+formatfloat(PyObject *v,
+            struct unicode_formatter_t *ctx,
+            struct unicode_format_arg_t *arg,
             PyObject **p_output,
             _PyUnicodeWriter *writer)
 {
@@ -111,8 +134,14 @@ formatfloat(PyObject *v, struct unicode_format_arg_t *arg,
     int dtoa_flags = 0;
 
     x = PyFloat_AsDouble(v);
-    if (x == -1.0 && PyErr_Occurred())
+    if (x == -1.0 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+            FORMAT_ERROR(PyExc_TypeError,
+                         "%%%c requires a real number, not %T",
+                         arg->ch, v);
+        }
         return -1;
+    }
 
     prec = arg->prec;
     if (prec < 0)
@@ -287,6 +316,7 @@ _PyUnicode_FormatLong(PyObject *val, int alt, int prec, int 
type)
  *       -1 and raise an exception on error */
 static int
 mainformatlong(PyObject *v,
+               struct unicode_formatter_t *ctx,
                struct unicode_format_arg_t *arg,
                PyObject **p_output,
                _PyUnicodeWriter *writer)
@@ -364,16 +394,14 @@ mainformatlong(PyObject *v,
         case 'o':
         case 'x':
         case 'X':
-            PyErr_Format(PyExc_TypeError,
-                    "%%%c format: an integer is required, "
-                    "not %.200s",
-                    type, Py_TYPE(v)->tp_name);
+            FORMAT_ERROR(PyExc_TypeError,
+                         "%%%c requires an integer, not %T",
+                         arg->ch, v);
             break;
         default:
-            PyErr_Format(PyExc_TypeError,
-                    "%%%c format: a real number is required, "
-                    "not %.200s",
-                    type, Py_TYPE(v)->tp_name);
+            FORMAT_ERROR(PyExc_TypeError,
+                         "%%%c requires a real number, not %T",
+                         arg->ch, v);
             break;
     }
     return -1;
@@ -381,15 +409,17 @@ mainformatlong(PyObject *v,
 
 
 static Py_UCS4
-formatchar(PyObject *v)
+formatchar(PyObject *v,
+           struct unicode_formatter_t *ctx,
+           struct unicode_format_arg_t *arg)
 {
     /* presume that the buffer is at least 3 characters long */
     if (PyUnicode_Check(v)) {
         if (PyUnicode_GET_LENGTH(v) == 1) {
             return PyUnicode_READ_CHAR(v, 0);
         }
-        PyErr_Format(PyExc_TypeError,
-                     "%%c requires an int or a unicode character, "
+        FORMAT_ERROR(PyExc_TypeError,
+                     "%%c requires an integer or a unicode character, "
                      "not a string of length %zd",
                      PyUnicode_GET_LENGTH(v));
         return (Py_UCS4) -1;
@@ -399,18 +429,18 @@ formatchar(PyObject *v)
         long x = PyLong_AsLongAndOverflow(v, &overflow);
         if (x == -1 && PyErr_Occurred()) {
             if (PyErr_ExceptionMatches(PyExc_TypeError)) {
-                PyErr_Format(PyExc_TypeError,
-                             "%%c requires an int or a unicode character, not 
%T",
+                FORMAT_ERROR(PyExc_TypeError,
+                             "%%c requires an integer or a unicode character, "
+                             "not %T",
                              v);
-                return (Py_UCS4) -1;
             }
             return (Py_UCS4) -1;
         }
 
         if (x < 0 || x > MAX_UNICODE) {
             /* this includes an overflow in converting to C long */
-            PyErr_SetString(PyExc_OverflowError,
-                            "%c arg not in range(0x110000)");
+            FORMAT_ERROR(PyExc_OverflowError,
+                         "%%c argument not in range(0x110000)%s", "");
             return (Py_UCS4) -1;
         }
 
@@ -438,12 +468,12 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
         /* Get argument value from a dictionary. Example: "%(name)s". */
         Py_ssize_t keystart;
         Py_ssize_t keylen;
-        PyObject *key;
         int pcount = 1;
 
         if (ctx->dict == NULL) {
-            PyErr_SetString(PyExc_TypeError,
-                            "format requires a mapping");
+            PyErr_Format(PyExc_TypeError,
+                         "format requires a mapping, not %T",
+                         ctx->args);
             return -1;
         }
         ++ctx->fmtpos;
@@ -460,25 +490,34 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
         }
         keylen = ctx->fmtpos - keystart - 1;
         if (ctx->fmtcnt < 0 || pcount > 0) {
-            PyErr_SetString(PyExc_ValueError,
-                            "incomplete format key");
+            PyErr_Format(PyExc_ValueError,
+                         "stray %% or incomplete format key at position %zd",
+                         arg->fmtstart);
             return -1;
         }
-        key = PyUnicode_Substring(ctx->fmtstr,
-                                  keystart, keystart + keylen);
-        if (key == NULL)
+        arg->key = PyUnicode_Substring(ctx->fmtstr,
+                                       keystart, keystart + keylen);
+        if (arg->key == NULL)
             return -1;
         if (ctx->args_owned) {
             ctx->args_owned = 0;
             Py_DECREF(ctx->args);
         }
-        ctx->args = PyObject_GetItem(ctx->dict, key);
-        Py_DECREF(key);
+        ctx->args = PyObject_GetItem(ctx->dict, arg->key);
         if (ctx->args == NULL)
             return -1;
         ctx->args_owned = 1;
-        ctx->arglen = -1;
-        ctx->argidx = -2;
+        ctx->arglen = -3;
+        ctx->argidx = -4;
+    }
+    else {
+        if (ctx->arglen < -1) {
+            PyErr_Format(PyExc_ValueError,
+                         "format requires a parenthesised mapping key "
+                         "at position %zd",
+                         arg->fmtstart);
+            return -1;
+        }
     }
 
     /* Parse flags. Example: "%+i" => flags=F_SIGN. */
@@ -497,17 +536,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
 
     /* Parse width. Example: "%10s" => width=10 */
     if (arg->ch == '*') {
-        v = unicode_format_getnextarg(ctx);
+        if (ctx->arglen < -1) {
+            PyErr_Format(PyExc_ValueError,
+                    "* cannot be used with a parenthesised mapping key "
+                    "at position %zd",
+                    arg->fmtstart);
+            return -1;
+        }
+        v = unicode_format_getnextarg(ctx, 0);
         if (v == NULL)
             return -1;
         if (!PyLong_Check(v)) {
-            PyErr_SetString(PyExc_TypeError,
-                            "* wants int");
+            FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
             return -1;
         }
         arg->width = PyLong_AsSsize_t(v);
-        if (arg->width == -1 && PyErr_Occurred())
+        if (arg->width == -1 && PyErr_Occurred()) {
+            if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+                FORMAT_ERROR(PyExc_OverflowError,
+                             "too big for width%s", "");
+            }
             return -1;
+        }
         if (arg->width < 0) {
             arg->flags |= F_LJUST;
             arg->width = -arg->width;
@@ -528,8 +578,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
                mixing signed and unsigned comparison. Since arg->ch is between
                '0' and '9', casting to int is safe. */
             if (arg->width > (PY_SSIZE_T_MAX - ((int)arg->ch - '0')) / 10) {
-                PyErr_SetString(PyExc_ValueError,
-                                "width too big");
+                PyErr_Format(PyExc_ValueError,
+                             "width too big at position %zd",
+                             arg->fmtstart);
                 return -1;
             }
             arg->width = arg->width*10 + (arg->ch - '0');
@@ -544,17 +595,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
             ctx->fmtpos++;
         }
         if (arg->ch == '*') {
-            v = unicode_format_getnextarg(ctx);
+            if (ctx->arglen < -1) {
+                PyErr_Format(PyExc_ValueError,
+                        "* cannot be used with a parenthesised mapping key "
+                        "at position %zd",
+                        arg->fmtstart);
+                return -1;
+            }
+            v = unicode_format_getnextarg(ctx, 0);
             if (v == NULL)
                 return -1;
             if (!PyLong_Check(v)) {
-                PyErr_SetString(PyExc_TypeError,
-                                "* wants int");
+                FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
                 return -1;
             }
             arg->prec = PyLong_AsInt(v);
-            if (arg->prec == -1 && PyErr_Occurred())
+            if (arg->prec == -1 && PyErr_Occurred()) {
+                if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+                    FORMAT_ERROR(PyExc_OverflowError,
+                                 "too big for precision%s", "");
+                }
                 return -1;
+            }
             if (arg->prec < 0)
                 arg->prec = 0;
             if (--ctx->fmtcnt >= 0) {
@@ -570,8 +632,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
                 if (arg->ch < '0' || arg->ch > '9')
                     break;
                 if (arg->prec > (INT_MAX - ((int)arg->ch - '0')) / 10) {
-                    PyErr_SetString(PyExc_ValueError,
-                                    "precision too big");
+                    PyErr_Format(PyExc_ValueError,
+                                 "precision too big at position %zd",
+                                 arg->fmtstart);
                     return -1;
                 }
                 arg->prec = arg->prec*10 + (arg->ch - '0');
@@ -589,8 +652,8 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
         }
     }
     if (ctx->fmtcnt < 0) {
-        PyErr_SetString(PyExc_ValueError,
-                        "incomplete format");
+        PyErr_Format(PyExc_ValueError,
+                     "stray %% at position %zd", arg->fmtstart);
         return -1;
     }
     return 0;
@@ -624,7 +687,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
     if (ctx->fmtcnt == 0)
         ctx->writer.overallocate = 0;
 
-    v = unicode_format_getnextarg(ctx);
+    v = unicode_format_getnextarg(ctx, 1);
     if (v == NULL)
         return -1;
 
@@ -660,7 +723,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
     case 'x':
     case 'X':
     {
-        int ret = mainformatlong(v, arg, p_str, writer);
+        int ret = mainformatlong(v, ctx, arg, p_str, writer);
         if (ret != 0)
             return ret;
         arg->sign = 1;
@@ -677,19 +740,19 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
             && !(arg->flags & (F_SIGN | F_BLANK)))
         {
             /* Fast path */
-            if (formatfloat(v, arg, NULL, writer) == -1)
+            if (formatfloat(v, ctx, arg, NULL, writer) == -1)
                 return -1;
             return 1;
         }
 
         arg->sign = 1;
-        if (formatfloat(v, arg, p_str, NULL) == -1)
+        if (formatfloat(v, ctx, arg, p_str, NULL) == -1)
             return -1;
         break;
 
     case 'c':
     {
-        Py_UCS4 ch = formatchar(v);
+        Py_UCS4 ch = formatchar(v, ctx, arg);
         if (ch == (Py_UCS4) -1)
             return -1;
         if (arg->width == -1 && arg->prec == -1) {
@@ -703,12 +766,38 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
     }
 
     default:
-        PyErr_Format(PyExc_ValueError,
-                     "unsupported format character '%c' (0x%x) "
-                     "at index %zd",
-                     (31<=arg->ch && arg->ch<=126) ? (char)arg->ch : '?',
-                     (int)arg->ch,
-                     ctx->fmtpos - 1);
+        if (arg->ch < 128 && Py_ISALPHA(arg->ch)) {
+            PyErr_Format(PyExc_ValueError,
+                         "unsupported format %%%c at position %zd",
+                         (int)arg->ch, arg->fmtstart);
+        }
+        else if (arg->ch == '\'') {
+            PyErr_Format(PyExc_ValueError,
+                         "stray %% at position %zd or unexpected "
+                         "format character \"'\" at position %zd",
+                         arg->fmtstart,
+                         ctx->fmtpos - 1);
+        }
+        else if (arg->ch >= 32 && arg->ch < 127) {
+            PyErr_Format(PyExc_ValueError,
+                         "stray %% at position %zd or unexpected "
+                         "format character '%c' at position %zd",
+                         arg->fmtstart,
+                         (int)arg->ch, ctx->fmtpos - 1);
+        }
+        else if (Py_UNICODE_ISPRINTABLE(arg->ch)) {
+            PyErr_Format(PyExc_ValueError,
+                         "stray %% at position %zd or unexpected "
+                         "format character '%c' (U+%04X) at position %zd",
+                         arg->fmtstart,
+                         (int)arg->ch, (int)arg->ch, ctx->fmtpos - 1);
+        }
+        else {
+            PyErr_Format(PyExc_ValueError,
+                         "stray %% at position %zd or unexpected "
+                         "format character U+%04X at position %zd",
+                         arg->fmtstart, (int)arg->ch, ctx->fmtpos - 1);
+        }
         return -1;
     }
     if (*p_str == NULL)
@@ -892,29 +981,40 @@ unicode_format_arg(struct unicode_formatter_t *ctx)
     arg.width = -1;
     arg.prec = -1;
     arg.sign = 0;
+    arg.fmtstart = ctx->fmtpos - 1;
+    arg.key = NULL;
     str = NULL;
 
     ret = unicode_format_arg_parse(ctx, &arg);
-    if (ret == -1)
-        return -1;
+    if (ret == -1) {
+        goto onError;
+    }
 
     ret = unicode_format_arg_format(ctx, &arg, &str);
-    if (ret == -1)
-        return -1;
+    if (ret == -1) {
+        goto onError;
+    }
 
     if (ret != 1) {
         ret = unicode_format_arg_output(ctx, &arg, str);
         Py_DECREF(str);
-        if (ret == -1)
-            return -1;
+        if (ret == -1) {
+            goto onError;
+        }
     }
 
     if (ctx->dict && (ctx->argidx < ctx->arglen)) {
+        // XXX: Never happens?
         PyErr_SetString(PyExc_TypeError,
                         "not all arguments converted during string 
formatting");
-        return -1;
+        goto onError;
     }
+    Py_XDECREF(arg.key);
     return 0;
+
+  onError:
+    Py_XDECREF(arg.key);
+    return -1;
 }
 
 
@@ -983,8 +1083,11 @@ PyUnicode_Format(PyObject *format, PyObject *args)
     }
 
     if (ctx.argidx < ctx.arglen && !ctx.dict) {
-        PyErr_SetString(PyExc_TypeError,
-                        "not all arguments converted during string 
formatting");
+        PyErr_Format(PyExc_TypeError,
+                     "not all arguments converted during string formatting "
+                     "(required %zd, got %zd)",
+                     ctx.arglen < 0 ? 0 : ctx.argidx,
+                     ctx.arglen < 0 ? 1 : ctx.arglen);
         goto onError;
     }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to