GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Compile time textwrap.dedent() equivalent for str or bytes literals · Issue #81087 · python/cpython

Compile time textwrap.dedent() equivalent for str or bytes literals #81087

New issue
Copy link
New issue
Copy link
Open
Open
Compile time textwrap.dedent() equivalent for str or bytes literals#81087
Copy link
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancementA feature request or enhancement

Description

@gpshead
gpshead
opened on May 13, 2019
Issue body actions
BPO 36906
Nosy @rhettinger, @terryjreedy, @gpshead, @stevendaprano, @methane, @serhiy-storchaka, @MojoVampire, @Carreau, @pablogsal, @remilapeyre, @Marco-Sulla, @iforapsy, @jtojnar
PRs
  • gh-81087: Add str.dedent and cache it at compile time #13445
  • 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/gpshead'
    closed_at = None
    created_at = <Date 2019-05-13.18:40:31.178>
    labels = ['interpreter-core', 'type-feature', '3.10']
    title = 'Compile time textwrap.dedent() equivalent for str or bytes literals'
    updated_at = <Date 2021-12-20.23:57:51.965>
    user = 'https://github.com/gpshead'

    bugs.python.org fields:

    activity = <Date 2021-12-20.23:57:51.965>
    actor = 'iforapsy'
    assignee = 'gregory.p.smith'
    closed = False
    closed_date = None
    closer = None
    components = ['Interpreter Core']
    creation = <Date 2019-05-13.18:40:31.178>
    creator = 'gregory.p.smith'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 36906
    keywords = ['patch']
    message_count = 46.0
    messages = ['342373', '342407', '342420', '342429', '342477', '342488', '342569', '342600', '342909', '342914', '342915', '342916', '342917', '342918', '342927', '342928', '342931', '342938', '342962', '342965', '342968', '342972', '343961', '343991', '350162', '356153', '356154', '356155', '356160', '356162', '356182', '356193', '356198', '356203', '356204', '365688', '381540', '381544', '381545', '381546', '381584', '381599', '381602', '381605', '382456', '398270']
    nosy_count = 14.0
    nosy_names = ['rhettinger', 'terry.reedy', 'gregory.p.smith', 'steven.daprano', 'methane', 'serhiy.storchaka', 'josh.r', 'mbussonn', 'pablogsal', 'remi.lapeyre', 'Marco Sulla', 'iforapsy', 'Miguel Amaral', 'jtojnar']
    pr_nums = ['13445']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue36906'
    versions = ['Python 3.10']

    Reacted by 叶芝秋, Segev Finer and Oliver Sanders

    Activity

    Next
    1. gpshead commented on May 13, 2019

      @gpshead
      gpshead
      on May 13, 2019
      MemberAuthor
      More actions

      A Python pattern in code is to keep everything indented to look pretty while, yet when the triple quoted multiline string in question needs to not have leading whitespace, calling textwrap.dedent("""long multiline constant""") is a common pattern.

      rather than doing this computation at runtime, this is something that'd make sense to do at compilation time. A natural suggestion for this would be a new letter prefix for multiline string literals that triggers this.

      Probably not worth "wasting" a letter on this, so I'll understand if we reject the idea, but it'd be nice to have rather than importing textwrap and calling it all over the place just for this purpose.

      There are many workarounds but an actual syntax would enable writing code that looked like this:

      class Castle:
          def __init__(self, name, lyrics=None):
              if not lyrics:
                  lyrics = df"""\
                  We're knights of the round table
                  We dance whene'er we're able
                  We do routines and scenes
                  With footwork impeccable.
                  We dine well here in {name}
                  We eat ham and jam and spam a lot.
                  """
              self._name = name
              self._lyrics = lyrics

      Without generating a larger temporary always in memory string literal in the code object that gets converted at runtime to the desired dedented form via a textwrap.dedent() call. I chose "d" as the the letter to mean dedent. I don't have a strong preference if we ever do make this a feature.

      Reacted by Feuermurmel
    2. gpshead
      added
      3.9 (EOL)end of lifeend of life
      interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
      type-featureA feature request or enhancementA feature request or enhancement
      on May 13, 2019
    3. rhettinger commented on May 14, 2019

      @rhettinger
      rhettinger
      on May 14, 2019
      Contributor
      More actions

      I agree that this is a recurring need and would be nice to have.

    4. stevendaprano commented on May 14, 2019

      @stevendaprano
      stevendaprano
      on May 14, 2019
      Member
      More actions

      +1

      There's a long thread on something similar here:

      https://mail.python.org/pipermail/python-ideas/2018-March/049564.html

      Carrying over into the following month:

      https://mail.python.org/pipermail/python-ideas/2018-April/049582.html

      Here's an even older thread:

      https://mail.python.org/pipermail/python-ideas/2010-November/008589.html

      In the more recent thread, I suggested that we give strings a dedent method. When called on a literal, the keyhole optimizer may do the dedent at compile time. Whether it does or not is a "quality of implementation" factor.

      The idea is to avoid the combinational explosion of yet another string prefix:

      urd'...'  # unicode raw string dedent
      

      while still making string dedents easily discoverable, and with a sufficiently good interpreter, string literals will be dedented at compile time avoiding any runtime cost:

      https://mail.python.org/pipermail/python-ideas/2018-March/049578.html

    5. gpshead commented on May 14, 2019

      @gpshead
      gpshead
      on May 14, 2019
      MemberAuthor
      More actions

      Oh good, I thought this had come up before. Your method idea that could be optimized on literals makes a lot of sense, and is notably more readable than yet another letter prefix.

    6. remilapeyre commented on May 14, 2019

      @remilapeyre
      remilapeyremannequin
      on May 14, 2019
      Mannequin
      More actions

      Hi, I have been looking to get more acquainted with the peephole optimizer. Is it okay if I work on this?

    7. gpshead commented on May 14, 2019

      @gpshead
      gpshead
      on May 14, 2019
      MemberAuthor
      More actions

      I'd say go for it. We can't guarantee we'll accept the feature yet, but I think the .dedent() method with an optimization pass approach is worthwhile making a proof of concept of regardless.

    8. stevendaprano commented on May 15, 2019

      @stevendaprano
      stevendaprano
      on May 15, 2019
      Member
      More actions

      For the record, I just came across this proposed feature for Java:

      https://openjdk.java.net/jeps/8222530

      Add text blocks to the Java language. A text block is a multi-line 
      string literal that avoids the need for most escape sequences, 
      automatically formats the string in predictable ways, and gives the 
      developer control over format when desired.
      

      It seems to be similar to Python triple-quoted strings except that the
      compiler automatically dedents the string using a "re-indentation
      algorithm". (Which sounds to me similar to, if not identical, to that
      used by textwrap.)

      The JEP proposal says:

      A study of Google's large internal repository of Java source code 
      showed that 98% of string literals, once converted to text blocks 
      and formatted appropriately, would require removal of incidental 
      white space. If Java introduced multi-line string solution without 
      support for automatically removing incidental white space, then many 
      developers would write a method to remove it themselves and/or lobby 
      for the String class to include a removal method.
      

      which matches my own experience: *most* but not all of my indented
      triple-quotes strings start with incidental whitespace that I don't care
      about. But not quite all, so I think backwards compatibility requires
      that *by default* triple-quoted strings are not dedented.

      Note that there are a couple of major difference between the JEP
      proposal and this:

      • The JEP proposes to automatically dedent triple-quoted strings;
        this proposal requires an explicit call to .dedent().

      • The JEP proposal allows the user to control the dedent by
        indenting, or not, the trailing end-quote;

      • however that means that in Java you won't be able to control
        the dedent if the string doesn't end with a final blank line;

      • Should the dedent method accept an optional int argument
        specifying the number of spaces to dedent by? (Defaulting to
        None, meaning "dedent by the common indent".) If so, that won't
        affect the compile-time optimization so long as the argument is
        a literal.

      • the JEP performs the dedent before backslash escapes are
        interpreted; in this proposal backslash escapes will
        occur before the dedent.

      The JEP also mentions considering multi-line string literals as Swift
      and Rust do them:

      https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

      https://stackoverflow.com/questions/29483365/what-is-the-syntax-for-a-multiline-string-literal

      I mention these for completeness, not to suggest them as alternatives.

    9. gpshead commented on May 15, 2019

      @gpshead
      gpshead
      on May 15, 2019
      MemberAuthor
      More actions

      Thanks, it's actually good to see this being a feature accepted in other languages.

    10. remilapeyre commented on May 20, 2019

      @remilapeyre
      remilapeyremannequin
      on May 20, 2019
      Mannequin
      More actions

      Hi @steven.daprano, @gregory.p.smith. I added the first version of my PR for review.

      One issue with it is that in:

      def f():
          return "   foo".dedent()

      f will have both " foo" and "foo" in its constants even if the first is not used anymore. Removing it requires looping over the code once more while marking the constants seen in a set and I was not sure if this was ok.

    11. serhiy-storchaka commented on May 20, 2019

      @serhiy-storchaka
      serhiy-storchaka
      on May 20, 2019
      Member
      More actions

      Perform the optimization at the AST level, not in the peepholer.

    12. stevendaprano commented on May 20, 2019

      @stevendaprano
      stevendaprano
      on May 20, 2019
      Member
      More actions

      One issue with it is that in:
      def f():
      return " foo".dedent()
      f will have both " foo" and "foo" in its constants even if the first is not used anymore.

      That seems to be what happens with other folded constants:

      >>> def f():
      ...     return 99.0 + 0.9
      ...
      >>> f.__code__.co_consts
      (None, 99.0, 0.9, 99.9)

      so I guess that this is okay for a first draft. One difference is that
      strings tend to be much larger than floats, so this will waste more
      memory. We ought to consider removing unused constants at some point.

      (But not me, sorry, I don't have enough C.)

      Removing it requires looping over the code once more while marking
      the constants seen in a set and I was not sure if this was ok.

      That should probably be a new issue.

    13. stevendaprano commented on May 20, 2019

      @stevendaprano
      stevendaprano
      on May 20, 2019
      Member
      More actions

      Serhiy's message crossed with mine -- you should probably listen to
      him over me :-)

    14. 35 remaining items

    15. jtojnar commented on Dec 3, 2020

      @jtojnar
      jtojnarmannequin
      on Dec 3, 2020
      Mannequin
      More actions

      One benefit of using a compile time feature over a runtime method is that the former allows for more predictable dedenting by first dedenting and only then interpolating variables.

      For example, the following code does not dedent the test string at all:

      import textwrap
      
      foo = "\n".join(["aaa", "bbb"])
      
      test = textwrap.dedent(
          f"""
          block xxx:
              {foo}
          """
      )

      It would be much nicer if we had syntactical dedents based on the leftmost non-whitespace column in the string, as supported by Nix language, for example.

      test = (
          df"""
          block xxx:
          {textwrap.indent(foo, ' '*4)}
          """
      )

      It would be even nicer if the interpolated strings could be indented based on the column the interpolation occurs in but that can at least be done at runtime with only minor inconvenience.

      Reacted by Inada Naoki
    16. stevendaprano commented on Jul 26, 2021

      @stevendaprano
      stevendaprano
      on Jul 26, 2021
      Member
      More actions

      This feature (or one very like it) has been requested again, this time with an "i" prefix:

      https://discuss.python.org/t/indented-multi-line-string-literals/9846/1

    17. ezio-melotti
      transferred this issue fromon Apr 10, 2022
    18. julianfortune commented on Jan 9, 2023

      @julianfortune
      julianfortune
      on Jan 9, 2023
      More actions

      What do folks think about taking an approach similar to Scala's stripMargin:

      >>> my_str = """
      ...    |Foo
      ...    |     Bar
      ...    |  Baz
      ...    """.stripmargin()
      Foo
          Bar
        Baz

      Seems it could be achieved with only the addition of a new method to str, no?

    19. stevendaprano commented on Jan 9, 2023

      @stevendaprano
      stevendaprano
      on Jan 9, 2023 via email
      Member
      More actions
      How is stripmargin different from dedent?
    20. AlexWaygood
      removed
      3.10 (EOL)end of lifeend of life
      on Jan 9, 2023
    21. julianfortune commented on Jan 10, 2023

      @julianfortune
      julianfortune
      on Jan 10, 2023
      More actions

      @stevendaprano my apologies, I did not realize dedent took into account shared whitespace; it seems dedent completely fills the need although it is a little more clunky that it is a separate function versus being implemented as a member of str.

    22. stevendaprano commented on Jan 10, 2023

      @stevendaprano
      stevendaprano
      on Jan 10, 2023 via email
      Member
      More actions
      The proposal is to make dedent a string method, and then allow interpreters to optimise it into a compile-time operation (perhaps in the keyhole optimiser), *not* to use a function like this: ``` def func(x): textwrap.dedent("""blah blah blah multiple lines """ ) ``` That won't work because it won't be seen as a docstring. This issue hasn't been touched for 18 months. It looks like this issue is languishing, possibly over the question whether the method should implement textwrap.dedent or inspect.cleandoc.
    23. gpshead commented on Jan 10, 2023

      @gpshead
      gpshead
      on Jan 10, 2023
      MemberAuthor
      More actions

      That question is valid but seems a mere implementation detail. A PR implementing the optimization would help motivate making that decision.

      I'd still love it to be automatic for docstrings as it'd save memory for everyone in the world, but that can be considered a separate follow-up feature.

    24. stevendaprano commented on Jan 11, 2023

      @stevendaprano
      stevendaprano
      on Jan 11, 2023 via email
      Member
      More actions
      cleandoc and dedent are functionally different, so it's not just an implementation detail, its a difference of semantics. Looking at the output of the two, I think cleandoc is the more correct behaviour for docstrings: ``` s = """First Second Third Indented fourth Fifth """ inspect.cleandoc(s) # --> 'First\nSecond\nThird\n Indented fourth\nFifth.' textwrap.dedent(s) # --> 'First\n Second\n Third\n Indented fourth\n Fifth.\n' ```
      I'd still love it to be automatic for docstrings as it'd save memory for everyone in the world,
      What are the consequences of just automatically dedenting docstrings? Technically its a breaking change, but is there code or people who rely on the current leading whitespace? help() already reformats the docstring, as does inspect, so maybe we should seriously consider just changing the rules for docstrings to automatically dedent them at function build time. It would probably have to go through a future import first.
    25. methane commented on Jan 11, 2023

      @methane
      methane
      on Jan 11, 2023
      Member
      More actions

      Looking at the output of the two, I think cleandoc is the more correct behaviour for docstrings:

      This issue is premierly about general multine string dedenting.
      So please forget about docstring now and consider what is the best dedent behevior for general multiline strings like SQL.

      s = """First
          Second
          Third
              Indented fourth
          Fifth
          """
      
      inspect.cleandoc(s) # --> 'First\nSecond\nThird\n    Indented fourth\nFifth.'
      textwrap.dedent(s)  # --> 'First\n    Second\n    Third\n        Indented fourth\n    Fifth.\n'
      

      I don't think special casing first line is not needed for general dedenting.

    26. stevendaprano commented on Jan 11, 2023

      @stevendaprano
      stevendaprano
      on Jan 11, 2023 via email
      Member
      More actions
      On Tue, Jan 10, 2023 at 10:02:47PM -0800, Inada Naoki wrote: > Looking at the output of the two, I think cleandoc is the more correct behaviour for docstrings: This issue is premierly about general multine string dedenting.
      But we already have a solution for that: the textwrap module. The textwrap module is inconvenient to use with docstrings, which is also when we want to put extra indents in the text block to make it look good in the .py file, but we need to remove that leading space before displaying the text. I agree that we should *also* think about other uses of dedent, but we should not forget docstrings. They are an important use-case, and as far as I am concerned, they are the driving motivation for moving dedent into the str type as a method.
    27. methane commented on Jan 12, 2023

      @methane
      methane
      on Jan 12, 2023
      Member
      More actions

      I agree that we should also think about other uses of dedent, but we
      should not forget docstrings. They are an important use-case, and as far
      as I am concerned, they are the driving motivation for moving dedent
      into the str type as a method.

      As I wrote in this comment, new syntax can be different from inspect.cleandoc().

      I prefer having the best multiline syntax by learning from Julia, Java, Swift, etc...

      Please read this comment too. Compile time docstring cleandoc is tracked in #81283.

    28. terryjreedy commented on Feb 11, 2023

      @terryjreedy
      terryjreedy
      on Feb 11, 2023
      Member
      More actions

      @Carreau "Is this a supposed to deprecating textwrap.dedent?"
      @stevendaprano "I don't think so, but eventually it might."
      I definitely feel that this should NOT be part of the initial PR without being discussed here.

      One of the removed uses in the PR is a 16 line, about 500 char, """-quoted string literal in idlelib. At the momemt I don't really like having 'dedent' moved from the starting line to the last. (I could use inspect.cleandoc instead. Or literal concatenation as elsewhere in the file. )

    29. gpshead
      removed their assignment
      on Jul 23, 2023
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

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

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        You are visitor 01402503 — GeezerVison does not store the pages you look up.
        Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.