Skip to content

Commit 5d81ab6

Browse files
qm2ksrinivasreddy
authored andcommitted
pythongh-71494: string.Formatter: support keys/attributes in unnumbered fields (pythonGH-21767)
1 parent 5f29b7e commit 5d81ab6

File tree

3 files changed

+28
-7
lines changed

3 files changed

+28
-7
lines changed

Lib/string.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -212,19 +212,20 @@ def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
212212
# this is some markup, find the object and do
213213
# the formatting
214214

215-
# handle arg indexing when empty field_names are given.
216-
if field_name == '':
215+
# handle arg indexing when empty field first parts are given.
216+
field_first, _ = _string.formatter_field_name_split(field_name)
217+
if field_first == '':
217218
if auto_arg_index is False:
218219
raise ValueError('cannot switch from manual field '
219220
'specification to automatic field '
220221
'numbering')
221-
field_name = str(auto_arg_index)
222+
field_name = str(auto_arg_index) + field_name
222223
auto_arg_index += 1
223-
elif field_name.isdigit():
224+
elif isinstance(field_first, int):
224225
if auto_arg_index:
225-
raise ValueError('cannot switch from manual field '
226-
'specification to automatic field '
227-
'numbering')
226+
raise ValueError('cannot switch from automatic field '
227+
'numbering to manual field '
228+
'specification')
228229
# disable auto arg incrementing, if it gets
229230
# used later on, then an exception will be raised
230231
auto_arg_index = False

Lib/test/test_string.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
import string
33
from string import Template
4+
import types
45

56

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

105+
def test_auto_numbering_lookup(self):
106+
fmt = string.Formatter()
107+
namespace = types.SimpleNamespace(foo=types.SimpleNamespace(bar='baz'))
108+
widths = [None, types.SimpleNamespace(qux=4)]
109+
self.assertEqual(
110+
fmt.format("{.foo.bar:{[1].qux}}", namespace, widths), 'baz ')
111+
112+
def test_auto_numbering_reenterability(self):
113+
class ReenteringFormatter(string.Formatter):
114+
def format_field(self, value, format_spec):
115+
if format_spec.isdigit() and int(format_spec) > 0:
116+
return self.format('{:{}}!', value, int(format_spec) - 1)
117+
else:
118+
return super().format_field(value, format_spec)
119+
fmt = ReenteringFormatter()
120+
x = types.SimpleNamespace(a='X')
121+
self.assertEqual(fmt.format('{.a:{}}', x, 3), 'X!!!')
122+
104123
def test_override_get_value(self):
105124
class NamespaceFormatter(string.Formatter):
106125
def __init__(self, namespace={}):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add attribute and item access support to :class:`string.Formatter` in auto-numbering mode, which allows format strings like '{.name}' and '{[1]}'.

0 commit comments

Comments
 (0)