Skip to content

Allow auto-numbered replacement fields in str.format() strings #49487

Description

@terryjreedy
BPO 5237
Nosy @gvanrossum, @terryjreedy, @mdickinson, @ncoghlan, @orsenthil, @pitrou, @ericvsmith, @ezio-melotti
Files
  • auto_number_formatter.py
  • auto_number_formatter_1.py
  • auto_number_formatter_2.py
  • auto_number_formatter_3.py
  • issue5237-0.patch
  • string27.txt: Revised string doc for implicit numeric arg names
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/ericvsmith'
    closed_at = <Date 2009-04-22.00:55:30.236>
    created_at = <Date 2009-02-12.23:28:48.259>
    labels = ['interpreter-core', 'type-feature']
    title = 'Allow auto-numbered replacement fields in str.format() strings'
    updated_at = <Date 2009-04-22.00:55:30.235>
    user = 'https://github.com/terryjreedy'

    bugs.python.org fields:

    activity = <Date 2009-04-22.00:55:30.235>
    actor = 'eric.smith'
    assignee = 'eric.smith'
    closed = True
    closed_date = <Date 2009-04-22.00:55:30.236>
    closer = 'eric.smith'
    components = ['Interpreter Core']
    creation = <Date 2009-02-12.23:28:48.259>
    creator = 'terry.reedy'
    dependencies = []
    files = ['13067', '13073', '13081', '13082', '13326', '13733']
    hgrepos = []
    issue_num = 5237
    keywords = ['patch']
    message_count = 43.0
    messages = ['81837', '81861', '81865', '81869', '81873', '81910', '81920', '81930', '81935', '81947', '81951', '81961', '82002', '82005', '83373', '83374', '83394', '83409', '83414', '83418', '83422', '83423', '83546', '83547', '83559', '83560', '83561', '83565', '83567', '83570', '83571', '83585', '83604', '83605', '86043', '86045', '86046', '86214', '86258', '86262', '86263', '86264', '86265']
    nosy_count = 9.0
    nosy_names = ['gvanrossum', 'terry.reedy', 'mark.dickinson', 'ncoghlan', 'orsenthil', 'pitrou', 'eric.smith', 'LambertDW', 'ezio.melotti']
    pr_nums = []
    priority = 'high'
    resolution = 'accepted'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue5237'
    versions = ['Python 3.1', 'Python 2.7']

    Activity

    1. terryjreedy commented on Feb 12, 2009

      @terryjreedy
      MemberAuthor

      3.x str.format() format strings denote replacement fields with braces
      {}. Currently, replacement fields *must* contain "either the numeric
      index of a positional argument, or the name of a keyword argument". [lib
      ref / builtin types / sequence types / string methods /str.format()]

      For simple sequential positional replacements, such as:
      "{0} is {1} {2}".format('Numbering fields', 'an annoying', 'nuisance')
      the writer has to do what computers are so good at, counting, and what
      was *not* required with % interpolation, where one could write more simply
      "%s is %s %s" % (('Auto-numbering', 'a convenient', 'feature')
      .
      Proposal: Allow field names to be omitted for all fields in a string and
      then default to 0, 1, ... so one could also write
      "{} is {} {}".format('Auto-numbering', 'a convenient', 'feature'

      This proposal is currently all or nothing for simplicity of description
      and presumed ease of implementation. The patch to the doc could then be
      "If all replacement fields are left blank, then sequential indexes 0,
      1, ... will be automatically inserted."
      inserted after the phrase quoted above. Mixing blank and non-blank
      specs would then be an error and raise an exception.

      This idea was posted today on Python-ideas thread "String formatting and
      named tuple". So far, +1 from Aahz, Raymond Hettinger, and Mathias
      Panzenbock.

    2. ericvsmith commented on Feb 13, 2009

      @ericvsmith
      Member

      It's easy enough to implement. Although the 'all-or-nothing' aspect is a
      little tough, I'll have to give it some thought.

      Maybe the best way to do this is to first create a string.Formatter
      subclass that implements it. I'll play with it and see what I come up with.

      From the description, I'm presuming we'd want:
      '{d}{s}{f}'.format(3, 'foo', 3.14)
      to work.

    3. lambertdw commented on Feb 13, 2009

      lambertdwmannequin
      Mannequin

      '{d}{s}{f}'.format(3, 'foo', 3.14)

      is possibly unclear,
      but is shorter than

      '{#d}{#s}{#f}'.format(...)

    4. lambertdw commented on Feb 13, 2009

      lambertdwmannequin
      Mannequin

      I am net yet fluent in format method. I meant

      ":" where "#" appeared.

      Anyway, I think you need the colon.

      If from
      print('{0:9}'.format(33))

      you make the argument number implicit and remove the colon you'd get

      print('{9}'.format(33))

      which does and should raise IndexError.

    5. ericvsmith commented on Feb 13, 2009

      @ericvsmith
      Member

      How is:
      '{d}{s}{f}'.format(3, 'foo', 3.14)

      more unclear than:
      '%d%s%f' % (3, 'foo', 3.14)
      ?

      But the more I think about it, the more I think it would have to be:
      '{:d}{:s}{:f}'.format(3, 'foo', 3.14)
      Since "{0:0}" is a legitimate format string, I need some way to tell
      whether "{0}" is missing the positional parameter or is missing the
      format specifier.

    6. mdickinson commented on Feb 13, 2009

      @mdickinson
      Member

      +1 for the general idea from me too, assuming that all the details can
      be worked out in a sane manner.

      Is it worth opening a discussion about this on comp.lang.python?

    7. ericvsmith commented on Feb 13, 2009

      @ericvsmith
      Member

      The attached file is a mostly working version that inherits from
      string.Formatter. It has the following shortcomings, which would all be
      addressed if we go forward:

      • Doesn't handle escaping '{' or '}'
      • Doesn't handle conversion specifiers, like '!s'

      These are all a function of me being too lazy to write a complete
      parser. If anyone really wants them, I could add them. But this is just
      a proof of concept.

      Admittedly this isn't a drop-in replacement for ''.format(), but it
      should give a taste of what using it would be like.

    8. pitrou commented on Feb 13, 2009

      @pitrou
      Member

      Unfortunately, '{d}{s}{f}'.format(3, 'foo', 3.14) can't work as you
      expect it to, because it already means "display the keyword arguments
      named d, s and f.

      (I agree that the syntax for format() strings is exceedingly tedious)

      On the other hand, '{:d}{:s}{:f}'.format(3, 'foo', 3.14) should be
      possible.

    9. lambertdw commented on Feb 13, 2009

      lambertdwmannequin
      Mannequin

      Answering first question msg81873.

      Without colon separator, this might be considered confusing:

      >>> (
      ...     '{d}{s}{f}{f}'.format(3, 'foo', 3.14, 2.72),
      ...     '{d}{s}{f}{f}'.format(d=3, s='foo', f=3.14)
      ... )
      ('3foo3.1400002.720000', '3foo3.143.14')
    10. ericvsmith commented on Feb 13, 2009

      @ericvsmith
      Member

      Right. The colon would be required if there's a format specifier. Or an
      exclamation if there's just a conversion specifier:

      "{!r}{:f}{!s:^10}".format('foo', 3, 10)
      would give:
      "'foo'3.000000 10 "

      I've attached a new version that includes format specifiers.

    11. terryjreedy commented on Feb 13, 2009

      @terryjreedy
      MemberAuthor

      All I am requesting is that
      '{} {} {}'.format(3, 'pi', 3.14) work as

      >>> '%s %s %s' % (3, 'pi', 3.14)
      '3 pi 3.14'
      >>> '{0} {1} {2}'.format(3, 'pi', 3.14)
      '3 pi 3.14'

      do today (3.0).

      I should note that the difference between typing {}, which is easy, and
      {1}, is more than just one keystroke because the latter requires
      unshift-1-shift

    12. ericvsmith commented on Feb 13, 2009

      @ericvsmith
      Member
      Terry J. Reedy wrote:
      > Terry J. Reedy <tjreedy@udel.edu> added the comment:
      > 
      > All I am requesting is that
      > '{} {} {}'.format(3, 'pi', 3.14) work as
      > 
      >>>> '%s %s %s' % (3, 'pi', 3.14)
      > '3 pi 3.14'
      >>>> '{0} {1} {2}'.format(3, 'pi', 3.14)
      > '3 pi 3.14'
      > 
      > do today (3.0).

      My string.Formatter subclass (attached to this bug report) does do this:

      $ ./python.exe 
      Python 2.7a0 (trunk:69516, Feb 11 2009, 14:30:31) 
      [GCC 4.0.1 (Apple Inc. build 5465)] on darwin
      Type "help", "copyright", "credits" or "license" for more information.
      >>> from auto_number_formatter_1 import MyFormatter
      >>> f = MyFormatter()
      >>> f.format('{} {} {}', 3, 'pi', 3.14)
      '3 pi 3.14'
      >>> 

      This is just for vetting the concept, if it's accepted I'll modify
      ''.format(). It's not a huge change. I just want to make sure that this
      implements what people are expecting.

      The talk about '{:d}' and the like is just to make sure all the cases
      are addressed. I doubt it would often be used that way.

      I should note that the difference between typing {}, which is easy, and
      {1}, is more than just one keystroke because the latter requires
      unshift-1-shift

      Agreed.

    13. self-assigned this
      on Feb 14, 2009
    14. 18 remaining items

    15. ncoghlan commented on Mar 14, 2009

      @ncoghlan
      Contributor

      Only Formatter.format_field() is particularly hard to override at the
      moment (for the same reason that __format__() methods are tricky to
      write). That said, creating a locale aware version of string formatting
      based on string.Formatter is such an obvious idea that I would actually
      be surprised if someone *hasn't* done it by now (they may not have
      published it anywhere, but I expect someone has implemented it for their
      own use).

      That said, I think it's OK for string.Formatter to lag the actual
      str.format implementation when it comes to features like this. I see it
      as similar to the formatting mini-language parser problem: the primary
      goal is to have a good implementation and syntax for str.format, while
      providing the tools to allow people to create alternative formatters
      with similar power is secondary.

    16. ericvsmith commented on Mar 14, 2009

      @ericvsmith
      Member

      Committed:
      r70364 (trunk)
      r70366 (py3k)

      The docs still need updating. If anyone with more knowledge of the
      documentation system than I have would like to tackle those, please feel
      free!

    17. terryjreedy commented on Mar 14, 2009

      @terryjreedy
      MemberAuthor

      Either Brandl or Peterson can and typically will change the .rst source
      if given the exact new text. For me to write that, I need to know the
      grammar you actually implemented. Did you, in essence, simply change

      field_name ::= (identifier | integer) ("." attribute_name | "["
      element_index "]")*

      to (in essence)

      field_name ::= (identifier | integer | ) ("." attribute_name |
      "[" element_index "]")*

      with the proviso that integers and blanks not be mixed in the same
      string, so that{.attr} and {[dex]} become legal? Or are those still
      illegal because only totally blank field names are allowed, so that the
      new field_name rule is essentially

      field_name ::= ((identifier | integer) ("." attribute_name | "["
      element_index "]")*) | ( )

      (with the same proviso).

      The existing doc text after the grammar box is slightly ambiguous or
      contradictory in that it first says that field names *are* ints or names
      and then says, correctly, that they *begin* with an int or name. (I
      would like to fix this in addition to adding a sentence.) Hence 'blank
      field name' can have two slightly different meanings and hence the
      question above.

    18. ericvsmith commented on Mar 14, 2009

      @ericvsmith
      Member

      I implemented this one:
      field_name ::= (identifier | integer | ) ("." attribute_name |
      "[" element_index "]")*

      Which I would have written as:
      field_name ::= (identifier | integer)? ("." attribute_name |
      "[" element_index "]")*

      Not that it matters, of course.

      And the proviso is correct: blanks and integers cannot be mixed in the
      same string.

      Thanks for looking at this!

    19. ericvsmith commented on Apr 16, 2009

      @ericvsmith
      Member

      Terry, are you still interested in documenting this (please say yes!)?
      I'm hoping it can be done by the beta release.

      Thanks.
      Eric.

    20. terryjreedy commented on Apr 16, 2009

      @terryjreedy
      MemberAuthor

      Yes, added to 'do in next few days' list.

    21. ericvsmith commented on Apr 16, 2009

      @ericvsmith
      Member

      Terry J. Reedy wrote:

      Terry J. Reedy <tjreedy@udel.edu> added the comment:

      Yes, added to 'do in next few days' list.

      Thanks so much.

    22. terryjreedy commented on Apr 21, 2009

      @terryjreedy
      MemberAuthor

      Suggested doc change for
      LibRef / StringServices / string.... / Format String Syntax

      1. In the grammar box, replace the field_name line with
        "
        field_name ::= arg_name ("." attribute_name | "[" element_index "]")*
        arg_name ::= (identifier | integer)?
        "
        The current text ambiguously uses "field_name" for both 'field_name' and
        'arg_name'. I found explaining the rule for blank arg_names in
        field_names easier and clearer with two separate unambiguous terms.

      Note: 'element_index' should be linked to its definition just as, for
      instance, 'attribute_name' is. Currently it is not.

      1. Revise the next two paragraphs as follows (using * to indicate
        grammar-term italics as in the current text -- except *all* is just for
        emphasis):
        "
        In less formal terms, the replacement field starts with a *field_name*
        that specifies the object whose value is to be formatted and inserted
        into the output instead of the replacement field. The *field_name* is
        optionally followed by a *conversion* field, which is preceded by an
        exclamation point '!', and a *format_spec*, which is preceded by a colon
        ':'. These specify a non-default format for the replacement value.

      The *field_name* itself begins with an *arg_name* that is either a
      number or a keyword. If it’s a number, it refers to a positional
      argument of the str.format() method, and if it’s a keyword it refers to
      a named keyword argument. If the numerical arg_names in a format string
      are 0, 1, 2, ... in sequence, they can *all* be omitted (not just some)
      and the numbers 0, 1, 2, ... will be automatically inserted in order.
      The *arg_name* can be followed by any number of index or attribute
      expressions. An expression of the form '.name' selects the named
      attribute using getattr(), while an expression of the form '[index]'
      does an index lookup using __getitem__().
      "
      Note: getattr and __getitem__ should be left linked as in the current text.

      1. In the following examples, add
        '''
        "From {} to {}" # Same as "From {0] to {1}"
    23. ericvsmith commented on Apr 21, 2009

      @ericvsmith
      Member

      Thanks, Terry. Your changes look reasonable to me. Can you commit them,
      or at least convert it to a patch against the existing docs?

    24. terryjreedy commented on Apr 21, 2009

      @terryjreedy
      MemberAuthor

      I am not a committer and cannot make patches. However, I did download
      http://svn.python.org/view/python/trunk/Doc/library/string.rst?revision=70650&view=markup,
      as string27.txt, carefully edit using the existing rst format, and
      upload. I trust you can make the patch and commit.

    25. terryjreedy commented on Apr 21, 2009

      @terryjreedy
      MemberAuthor

      PS. I first edited
      http://svn.python.org/view/python/branches/py3k/Doc/library/strings.rst?revision=63803&view=markup
      but then noticed that this goes in 2.7. My impression is that the
      procedure is to commit to trunk and then forward port, so I redid it as
      indicated above. But if you are going from 3.1 to 2.7, I will upload
      the 3.1 revision.

    26. ericvsmith commented on Apr 22, 2009

      @ericvsmith
      Member

      Thanks, Terry.

      I think the only changes I'll make are:

      arg_name: (identifier | integer)*
      should be:
      arg_name: (identifier | integer)?

      And leave:
      conversion: "r" | "s"
      instead of:
      conversion: "r" | "s" | "a"
      because 'a' isn't valid in 2.7.

    27. ericvsmith commented on Apr 22, 2009

      @ericvsmith
      Member

      Documentation changes checked into trunk in r71788 and py3k in r71790.

      Issue closed.

    28. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions