diff --git a/Lib/string.py b/Lib/string.py index 2eab6d4f595c4e..c4f05c7223ce8a 100644 --- a/Lib/string.py +++ b/Lib/string.py @@ -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 diff --git a/Lib/test/test_string.py b/Lib/test/test_string.py index 824b89ad517c12..654c23df98b39a 100644 --- a/Lib/test/test_string.py +++ b/Lib/test/test_string.py @@ -1,6 +1,7 @@ import unittest import string from string import Template +import types class ModuleTest(unittest.TestCase): @@ -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') + print(fmt.format('{.a:{}}', x, 3), 'X!!!') + def test_override_get_value(self): class NamespaceFormatter(string.Formatter): def __init__(self, namespace={}): diff --git a/Misc/NEWS.d/next/Library/2020-08-07-16-55-57.bpo-27307.Xqzzda.rst b/Misc/NEWS.d/next/Library/2020-08-07-16-55-57.bpo-27307.Xqzzda.rst new file mode 100644 index 00000000000000..6e7a856d994cb6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-08-07-16-55-57.bpo-27307.Xqzzda.rst @@ -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]}'.