Skip to content

Commit

Permalink
pythongh-71494: string.Formatter: support keys/attributes in unnumber…
Browse files Browse the repository at this point in the history
…ed fields (pythonGH-21767)
  • Loading branch information
qm2k authored Jan 31, 2025
1 parent 0373926 commit 22b2d37
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 7 deletions.
15 changes: 8 additions & 7 deletions Lib/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,20 @@ def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
# this is some markup, find the object and do
# the formatting

# handle arg indexing when empty field_names are given.
if field_name == '':
# handle arg indexing when empty field first parts are given.
field_first, _ = _string.formatter_field_name_split(field_name)
if field_first == '':
if auto_arg_index is False:
raise ValueError('cannot switch from manual field '
'specification to automatic field '
'numbering')
field_name = str(auto_arg_index)
field_name = str(auto_arg_index) + field_name
auto_arg_index += 1
elif field_name.isdigit():
elif isinstance(field_first, int):
if auto_arg_index:
raise ValueError('cannot switch from manual field '
'specification to automatic field '
'numbering')
raise ValueError('cannot switch from automatic field '
'numbering to manual field '
'specification')
# disable auto arg incrementing, if it gets
# used later on, then an exception will be raised
auto_arg_index = False
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_string.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
import string
from string import Template
import types


class ModuleTest(unittest.TestCase):
Expand Down Expand Up @@ -101,6 +102,24 @@ def test_index_lookup(self):
with self.assertRaises(KeyError):
fmt.format("{0[2]}{0[0]}", {})

def test_auto_numbering_lookup(self):
fmt = string.Formatter()
namespace = types.SimpleNamespace(foo=types.SimpleNamespace(bar='baz'))
widths = [None, types.SimpleNamespace(qux=4)]
self.assertEqual(
fmt.format("{.foo.bar:{[1].qux}}", namespace, widths), 'baz ')

def test_auto_numbering_reenterability(self):
class ReenteringFormatter(string.Formatter):
def format_field(self, value, format_spec):
if format_spec.isdigit() and int(format_spec) > 0:
return self.format('{:{}}!', value, int(format_spec) - 1)
else:
return super().format_field(value, format_spec)
fmt = ReenteringFormatter()
x = types.SimpleNamespace(a='X')
self.assertEqual(fmt.format('{.a:{}}', x, 3), 'X!!!')

def test_override_get_value(self):
class NamespaceFormatter(string.Formatter):
def __init__(self, namespace={}):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add attribute and item access support to :class:`string.Formatter` in auto-numbering mode, which allows format strings like '{.name}' and '{[1]}'.

0 comments on commit 22b2d37

Please sign in to comment.