This PEP proposes to add a feature that automatically removes indentation frommultiline string literals.
Dedented multiline strings use a new prefix “d” (shorthand for “dedent”) beforethe opening quote of a multiline string literal.
Example (spaces are visualized as.):
defhello_paragraph()->str:....returnd"""........<p>..........Hello, World!........</p>...."""
Unliketextwrap.dedent(), indentation before closing quotes is alsoconsidered when determining the amount of indentation to be removed.Therefore, the string returned in the example above consists of the followingthree lines.
"....<p>\n""......Hello,World!\n""....</p>\n"When writing multiline string literals within deeply indented Python code,users are faced with the following choices:
textwrap.dedent() to remove indentation.All of these options have drawbacks in terms of code readability andmaintainability.
"\n" at the end of each line is tedious.It’s easy to miss the semicolons between many string concatenations.textwrap.dedent() is implemented in Python so it requires some runtimeoverhead. Moreover, it cannot be used to dedent t-strings.This PEP aims to provide a built-in syntax for dedented multiline strings thatis both easy to read and write, while also being efficient at runtime.
The main alternative to this idea is to implementtextwrap.dedent() in Cand provide it as astr.dedent() method.This idea reduces the runtime overhead oftextwrap.dedent().By making it a built-in method, it also allows for compile-time dedentationwhen called directly on string literals.
However, this approach has several drawbacks:
dedent() method would need to accept an argument specifyingthe amount of indentation to remove.This would be cumbersome and error-prone for users.str.dedent() cannot dedent the whole string.str objects, so they cannot use thestr.dedent() method.While adding adedent() method tostring.templatelib.Template is anoption, it would lead to inconsistency since t-strings and f-strings are verysimilar but would have different behaviors regarding dedentation.Thestr.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 dedicatedsyntax is superior.
Add a new string literal prefix “d” for dedented multiline strings.This prefix can be combined with “f”, “t”, “r”, and “b” prefixes.
This prefix is only for multiline string literals.So it can only be used with triple quotes (""" or''').
Opening triple quotes needs to be followed by a newline character.This newline is not included in the resulting string.The content of the d-string starts from the next line.
Indentation is leading whitespace characters (spaces and tabs) of each line.
The amount of indentation to be removed is determined by the longest commonindentation of lines in the string.Lines consisting entirely of whitespace characters are ignored whendetermining the common indentation, except for the line containing the closingtriple quotes.
Spaces and tabs are treated as different characters.For example,"hello" and"\thello" have no common indentation.
The dedentation process removes the determined indentation from every line inthe string.
IndentationError.The determined indentation is removed from these lines.IndentationError.These lines become empty lines.Unless combined with the “r” prefix, backslash escapes are processed afterthe dedentation process.So you cannot use\\t in indentations.And you can use line continuation (backslash at the end of line) and removeindentation from the continued line.
Examples:
# d-string must starts with a newline.s=d""# SyntaxError: d-string must be triple-quoteds=d""""""# SyntaxError: d-string must start with a newlines=d"""Hello"""# SyntaxError: d-string must start with a newlines=d"""Hello..World!"""# SyntaxError: d-string must start with a newline# d-string removes the longest common indentation from each line.# Empty lines are ignored, but closing quotes line is always considered.s=d"""..Hello..World!.."""print(repr(s))# 'Hello\nWorld!\n's=d"""..Hello..World!."""print(repr(s))# '.Hello\n.World!\n's=d"""..Hello..World!"""print(repr(s))# '..Hello\n..World!\n's=d"""..Hello...World!..."""# Longest common indentation is '..'.print(repr(s))# 'Hello\n\n\nWorld!\n.'# Closing qutotes can be on the same line as the last content line.# In this case, the string does not end with a newline.s=d"""..Hello..World!"""print(repr(s))# 'Hello\nWorld!'# Tabs are allowed as indentation.# But tabs and spaces are treated as different characters.s=d"""--->..Hello--->..World!--->"""print(repr(s))# '..Hello\n..World!\n's=d"""--->Hello..World!.."""# There is no common indentation.print(repr(s))# '\tHello\n..World!\n..'# Line continuation with backslash works as usual.# But you cannot put a backslash right after the opening quotes.s=d"""..Hello\..World!\.."""print(repr(s))# 'Hello World!'s=d"""\..Hello..World.."""# SyntaxError: d-string must starts with a newline.# d-string can be combined with r-string, b-string, f-string, and t-string.s=dr"""..Hello\..World!\.."""print(repr(s))# 'Hello\\\nWorld!\\\n's=db"""..Hello..World!.."""print(repr(s))# b'Hello\nWorld!\n's=df"""....Hello, {"world".title()}!...."""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',)
The main difference betweentextwrap.dedent("""...""") and d-string can beexplained as follows:
textwrap.dedent() is a regular function, but d-string is part of thelanguage syntax. d-string has no runtime overhead, and it can removeindentation from t-strings.textwrap.dedent(), you need to start with"""\ to avoidincluding the first newline character, but with d-string, the string contentstarts from the line afterd""", so no backslash is needed.importtextwraps1=textwrap.dedent("""\ Hello World! """)s2=d""" Hello World! """asserts1==s2
textwrap.dedent() ignores all blank lines when determining the commonindentation, but d-string also considers the indentation of the closingquotes.This allows d-string to preserve some indentation in the result when needed.importtextwraps1=textwrap.dedent("""\ Hello World! """)s2=d""" Hello World! """asserts1!=s2asserts1=='Hello\nWorld!\n'asserts2==' Hello\n World!\n'
importtextwraps1=textwrap.dedent("""\ Lorem ipsum dolor sit amet, consectetur adipiscing elit,\sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\nisi ut aliquip ex ea commodo consequat. """)s2=d""" Lorem ipsum dolor sit amet, consectetur adipiscing elit,\ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\ nisi ut aliquip ex ea commodo consequat. """asserts1==s2
Java 15 introduced a feature calledtext blocks.Since Java had not used triple qutes before, they introduced triple quotes formultiline string literals with automatic indent removal.
C# 11 also introduced a similar feature calledraw string literals.
Julia andSwiftalso support triple-quoted string literals that automatically remove indentation.
PHP 7.3 introducedFlexible Heredoc and Nowdoc SyntaxesAlthough it uses closing marker (e.g.<<<END...END) instead oftriple quote, it removes indent from text too.
Ruby also has“squiggly” heredocthat removes indent from lines in heredoc.
Java, Julia, and Ruby uses the least-indented line to determine the amount ofindentation to be removed.Swift, C#, and PHP uses the indentation of the closing triple quotes orclosing marker.
A CPython implementation of PEP 822 is available atmethane/cpython#108.
str.dedent() methodAs mentioned in the Rationale section, this PEP doesn’t reject the idea of astr.dedent() method.A faster version oftextwrap.dedent() implemented in C would be useful forruntime dedentation.
However, d-string is more suitable for multiline string literals because:
It is considered thatusing triple backticksfor dedented multiline strings could be an alternative syntax.This notation is familiar to us from Markdown. While there were past concernsabout certain keyboard layouts,nowadays many people are accustomed to typing this notation.
However, this notation conflicts when embedding Python code within Markdown orvice versa.Therefore, considering these drawbacks, increasing the variety of quotecharacters is not seen as a superior idea compared to adding a prefix tostring literals.
__future__ importInstead of adding a prefix to string literals, the idea of using a__future__ import to change the default behavior of multilinestring 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 bestraightforward.Until all multiline strings in that source code are rewritten tothe 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 prefixover the__future__ import.
Another idea considered was to remove the newline character from the last line.This idea is same to Swift’s multiline string literals.
With this idea, user can write multiline string having indent without trailingnewline like below:
s=d""" Hello World! """# " Hello\n World!" (no trailing newline)s=d""" Hello World! """# " Hello\n World!\n" (has a trailing newline)
However, including a newline at the end of the last line of a multiline stringliteral is a very common case, and requiring an empty line at the end wouldlook quite unnatural compared to Python’s traditional multiline string literals.
When usingtextwrap.dedent("""..."""), in many cases users had to write abackslash right after the opening quote, which was frustrating.Therefore, not including the newline after the opening quote in d-strings isa clear improvement for users.On the other hand, removing the newline at the end of the line before theclosing quote would likely cause confusion when rewriting code that usestextwrap.dedent("""...""").
Without this idea, if you don’t need remaining indentation but want to avoid atrailing newline, you can put the closing quotes on the same line as the lastcontent line.And if you need to retain some indentation without a trailing newline,you can use workarounds such as line continuation orstr.rstrip().
s=d""" Hello World!"""asserts=="Hello\nWorld!"s=d""" Hello World!\ """asserts==" Hello\n World!"s=dr""" Hello World! """.rstrip()asserts==" Hello\n World!"
While these workarounds are not ideal, the drawbacks are considered smallerthan the confusion that would result from automatically removing the trailingnewline.
Sincetextwrap.dedent() does not consider the indentation of the closingquotes, these workarounds are not necessary when rewritingtextwrap.dedent()to d-strings.
This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0822.rst
Last modified:2026-01-21 14:56:13 GMT