Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-71494: string.Formatter unnumbered key/attributes #21767

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
qm2k marked this conversation as resolved.
Show resolved Hide resolved
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]}'.
Loading