-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
PEP 822: Dedented Multiline String (d-string) #4768
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
862cdfa
PEP 822: Dedented Multiline String (d-string)
methane 67ff671
use _ to visualize spaces
methane faad770
add link to the PEP thread in Discourse
methane 174ed47
fixup
methane cb45c5d
add note about PHP 7.3 flexible heredoc/nowdoc
methane f0fc793
fix date format
methane 976a5fb
fix lint error about backquotes
methane 32886d9
Apply suggestions from code review
methane 5de9ea2
remove idea threads from Post-History
methane 69d2b23
Apply suggestion from @hugovk
methane efa0814
reflow
methane File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,292 @@ | ||
| PEP: 822 | ||
| Title: Dedented Multiline String (d-string) | ||
| Author: Inada Naoki <[email protected]> | ||
| Discussions-To: https://discuss.python.org/t/105519 | ||
| Status: Draft | ||
| Type: Standards Track | ||
| Created: 05-Jan-2026 | ||
| Python-Version: 3.15 | ||
| Post-History: `2023-07-23 <https://discuss.python.org/t/30127>`__, | ||
| `2023-10-11 <https://discuss.python.org/t/35907>`__, | ||
| `2023-12-09 <https://discuss.python.org/t/40679>`__, | ||
| `2025-05-07 <https://discuss.python.org/t/90988>`__, | ||
| `2026-01-05 <https://discuss.python.org/t/105519>`__, | ||
|
|
||
|
|
||
| Abstract | ||
| ======== | ||
|
|
||
| This PEP proposes to add a feature that automatically removes indentation from multiline string literals. | ||
|
|
||
| Dedented multiline strings use a new prefix "d" (shorthand for "dedent") before the opening quote of a multiline string literal. | ||
|
|
||
| Example (spaces are visualized as `_`): | ||
hugovk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| .. code-block:: python | ||
|
|
||
| def hello_paragraph() -> str: | ||
| ____return d""" | ||
| ________<p> | ||
| __________Hello, World! | ||
| ________</p> | ||
| ____""" | ||
|
|
||
| The closing triple quotes control how much indentation would be removed. | ||
| In the above example, the returned string will contain three lines | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| * ``"____<p>\n"`` (four leading spaces) | ||
| * ``"______Hello, World!\n"`` (six leading spaces) | ||
| * ``"____</p>\n"`` (four leading spaces) | ||
|
|
||
|
|
||
| Motivation | ||
| ========== | ||
|
|
||
| When writing multiline string literals within deeply indented Python code, | ||
| users are faced with the following choices: | ||
|
|
||
| * Accept that the content of the string literal will be left-aligned. | ||
| * Use multiple single-line string literals concatenated together instead of a multiline string literal. | ||
| * Use ``textwrap.dedent()`` to remove indentation. | ||
|
|
||
| All of these options have drawbacks in terms of code readability and maintainability. | ||
|
|
||
| * Left-aligned multiline strings look awkward and tend to be avoided. | ||
| In practice, many places including Python's own test code choose other methods. | ||
| * Concatenated single-line string literals are more verbose and harder to maintain. | ||
| * ``textwrap.dedent()`` is implemented in Python so it require some runtime overhead. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| It cannot be used in hot paths where performance is critical. | ||
|
|
||
| This PEP aims to provide a built-in syntax for dedented multiline strings that | ||
| is both easy to read and write, while also being efficient at runtime. | ||
|
|
||
|
|
||
| Rationale | ||
| ========= | ||
|
|
||
| The main alternative to this idea is to implement ``textwrap.dedent()`` in C | ||
| and provide it as a ``str.dedent()`` method. | ||
| This idea reduces the runtime overhead of ``textwrap.dedent()``. | ||
| By making it a built-in method, it also allows for compile-time dedentation | ||
| when called directly on string literals. | ||
|
|
||
| However, this approach has several drawbacks: | ||
|
|
||
| * To support cases where users want to include some indentation in the string, | ||
| the ``dedent()`` method would need to accept an argument specifying | ||
| the amount of indentation to remove. | ||
| This would be cumbersome and error-prone for users. | ||
| * When continuation lines (lines after line ends with a backslash) are used, | ||
| they cannot be dedented. | ||
| * f-strings may interpolate expressions as multiline string without indent. | ||
| In such case, f-string + ``str.dedent()`` cannot dedent the whole string. | ||
| * t-strings do not create str objects, so they cannot use the ``str.dedent()`` method. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| While adding a ``dedent()`` method to ``string.templatelib.Template`` is an option, | ||
| it would lead to inconsistency since t-strings and f-strings are very similar but | ||
| would have different behaviors regarding dedentation. | ||
|
|
||
| The ``str.dedent()`` method can still be useful for non-literal strings, | ||
| so this PEP does not preclude that idea. | ||
| However, for ease of use with multiline string literals, providing dedicated | ||
| syntax is superior. | ||
|
|
||
|
|
||
| Specification | ||
| ============= | ||
|
|
||
| Add a new string literal prefix "d" for dedented multiline strings. | ||
| This prefix can be combined with "f", "t", and "r" prefixes. | ||
|
|
||
| This prefix is only for multiline string literals. | ||
| So it can only be used with triple quotes (``"""`` or ``'''``). | ||
| Using it with single or double quotes (``"`` or ``'``) is a syntax error. | ||
|
|
||
| Opening triple quotes needs to be followed by a newline character. | ||
| This newline is not included in the resulting string. | ||
|
|
||
| The amount of indentation to be removed is determined by the whitespace | ||
| (``' '`` or ``'\t'``) preceding the closing triple quotes. | ||
| Mixing spaces and tabs in indentation raises a TabError, similar to Python's | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| own indentation rules. | ||
|
|
||
| The dedentation process removes the determined amount of leading whitespace from each line in the string. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Lines that are shorter than the determined indentation become just an empty line (e.g. ``"\n"``). | ||
| Otherwise, if the line does not start with the determined indentation, | ||
| Python raises an ``IndentationError``. | ||
|
|
||
| Unless combined with the "r" prefix, backslash escapes are processed after removing indentation. | ||
| So you cannot use ``\\t`` to create indentation. | ||
| And you can use line continuation (backslash at the end of line) and remove indentation from the continued line. | ||
|
|
||
| Examples: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # whiltespace is shown as _ and TAB is shown as ---> for clarity. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # Error messages are just for explanation. Actual messages may differ. | ||
|
|
||
| s = d"" # SyntaxError: d-string must be a multiline string | ||
| s = d"""Hello""" # SyntaxError: d-string must be a multiline string | ||
| s = d"""Hello | ||
| __World! | ||
| """ # SyntaxError: d-string must start with a newline | ||
|
|
||
| s = d""" | ||
| __Hello | ||
| __World!""" # SyntaxError: d-string must end with an indent-only line | ||
|
|
||
| s = d""" | ||
| __Hello | ||
| __World! | ||
| """ # Zero indentation is removed because closing quotes are not indented. | ||
| print(repr(s)) # '__Hello\n__World!\n' | ||
|
|
||
| s = d""" | ||
| __Hello | ||
| __World! | ||
| _""" # One space indentation is removed. | ||
| print(repr(s)) # '_Hello\n_World!\n' | ||
|
|
||
| s = d""" | ||
| __Hello | ||
| __World! | ||
| __""" # Two spaces indentation are removed. | ||
| print(repr(s)) # 'Hello\nWorld!\n' | ||
|
|
||
| s = d""" | ||
| __Hello | ||
| __World! | ||
| ___""" # IndentationError: missing valid indentation | ||
|
|
||
| s = d""" | ||
| --->Hello | ||
| __World! | ||
| __""" # IndentationError: missing valid indentation | ||
|
|
||
| s = d""" | ||
| --->--->__Hello | ||
| --->--->__World! | ||
| --->--->""" # TAB is allowed as indentation. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # Spaces are just in the string, not indentation to be removed. | ||
| print(repr(s)) # '__Hello\n__World!\n' | ||
|
|
||
| s = d""" | ||
| --->____Hello | ||
| --->____World! | ||
| --->__""" # TabError: mixing spaces and tabs in indentation | ||
|
|
||
| s = d""" | ||
| __Hello \ | ||
| __World!\ | ||
| __""" # line continuation works as ususal | ||
| print(repr(s)) # 'Hello_World!' | ||
|
|
||
| s = d"""\ | ||
| __Hello | ||
| __World | ||
| __""" # SyntaxError: d-string must starts with a newline. | ||
|
|
||
| s = dr""" | ||
| __Hello\ | ||
| __World!\ | ||
| __""" # d-string can be combined with r-string. | ||
| print(repr(s)) # 'Hello\\\nWorld!\\\n' | ||
|
|
||
| s = df""" | ||
| ____Hello, {"world".title()}! | ||
| ____""" # d-string can be combined with f-string and t-string too. | ||
| print(repr(s)) # 'Hello, World!\n' | ||
|
|
||
| s = dt""" | ||
| ____Hello, {"world".title()}! | ||
| ____""" | ||
| print(type(s)) # <class 'string.templatelib.Template'> | ||
| print(s.strings) # ('Hello, ', '!\n') | ||
| print(s.values) # ('World',) | ||
| print(s.interpolations) # (Interpolation('World', '"world".title()', None, ''),) | ||
|
|
||
|
|
||
| How to Teach This | ||
| ================= | ||
|
|
||
| In the tutorial, we can introduce d-string with triple quote string literals. | ||
| Additionally, we can add a note in the ``textwrap.dedent()`` documentation, | ||
| providing a link to the d-string section in the language reference or the relevant part of the tutorial. | ||
|
|
||
|
|
||
| Other Languages having Similar Features | ||
| ======================================== | ||
|
|
||
| Java 15 introduced a feature called `Text Blocks <https://openjdk.org/jeps/378>`__. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Since Java had not used triple qutes before, they introduced triple quotes for multiline string literals with automatic indent removal. | ||
|
|
||
| C# 11 also introduced a similar feature called `Raw String Literals <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/raw-string-literal>`__. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| `Julia <https://docs.julialang.org/en/v1/manual/strings/#Triple-Quoted-String-Literals>`__ and | ||
| `Swift <https://docs.swift.org/swift-book/documentation/the-swift-programming-language/stringsandcharacters/#Multiline-String-Literals>`__ | ||
| also support triple-quoted string literals that automatically remove indentation. | ||
|
|
||
| PHP 7.3 introduced `Flexible Heredoc and Nowdoc Syntaxes <https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes>`__ | ||
| Although it uses closing marker (e.g. ``<<<END ... END``) instead of triple quote, | ||
| it removes indent from text too. | ||
|
|
||
| Java and Julia uses the least-indented line to determine the amount of indentation to be removed. | ||
| Swift, C#, and PHP uses the indentation of the closing triple quotes or closing marker. | ||
|
|
||
| This PEP chose the Swift and C# approach because it is more simple and easy to explain. | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| Reference Implementation | ||
| ======================== | ||
|
|
||
| A CPython implementation of PEP 822 is available at | ||
| `methane/cpython#108 <https://github.com/methane/cpython/pull/108>`__. | ||
|
|
||
|
|
||
| Rejected Ideas | ||
| ============== | ||
|
|
||
| ``str.dedent()`` method | ||
| ------------------------ | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| As mentioned in the Rationale section, this PEP doesn't reject the idea of a ``str.dedent()`` method. | ||
| A faster version of ``textwrap.dedent()`` implemented in C would be useful for runtime dedentation. | ||
|
|
||
| However, d-string is more suitable for multiline string literals because: | ||
|
|
||
| * It works well with f/t-strings. | ||
| * It allows specifying the amount of indentation to be removed more easily. | ||
| * It can dedent continuation lines. | ||
|
|
||
|
|
||
| triple-backquote | ||
| ----------------- | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| It is considered that `using triple backquotes ("\`\`\`") <https://discuss.python.org/t/40679>`__ | ||
hugovk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for dedented multiline strings could be an alternative syntax. | ||
| This notation is familiar to us from Markdown. While there were past concerns about certain keyboard layouts, | ||
| nowadays many people are accustomed to typing this notation. | ||
|
|
||
| However, this notation conflicts when embedding Python code within Markdown or vice versa. | ||
| Therefore, considering these drawbacks, increasing the variety of quote | ||
| characters is not seen as a superior idea compared to adding a prefix to string literals. | ||
|
|
||
|
|
||
| ``__future__`` import | ||
| --------------------- | ||
| Instead of adding a prefix to string literals, the idea of using a ``__future__`` | ||
methane marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import to change the default behavior of multiline string literals was also considered. | ||
| This could help simplify Python's grammar in the future. | ||
|
|
||
| But rewriting all existing complex codebases to the new notation may not be straightforward. | ||
| Until all multiline strings in that source code are rewritten to the new notation, automatic dedentation cannot be utilized. | ||
|
|
||
| Until all users can rewrite existing codebases to the new notation, two types of Python syntax will coexist indefinitely. | ||
| Therefore, `many people preferred the new string prefix <https://discuss.python.org/t/90988/54>`__ over the ``__future__`` import. | ||
|
|
||
|
|
||
| Copyright | ||
| ========= | ||
|
|
||
| This document is placed in the public domain or under the | ||
| CC0-1.0-Universal license, whichever is more permissive. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.