Skip to content

Commit 862cdfa

Browse files
committed
PEP 822: Dedented Multiline String (d-string)
1 parent 152a687 commit 862cdfa

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed

peps/pep-0822.rst

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
PEP: 822
2+
Title: Dedented Multiline String (d-string)
3+
Author: Inada Naoki <[email protected]>
4+
Discussions-To: TBD
5+
Status: Draft
6+
Type: Standards Track
7+
Created: 05-Jan-2026
8+
Python-Version: 3.15
9+
Post-History: `2023-07-23 <https://discuss.python.org/t/30127>`__,
10+
`2023-10-11 <https://discuss.python.org/t/35907>`__,
11+
`2023-12-09 <https://discuss.python.org/t/40679>`__,
12+
`2025-05-07 <https://discuss.python.org/t/90988>`__,
13+
TBD
14+
15+
16+
Abstract
17+
========
18+
19+
This PEP proposes to add a feature that automatically removes indentation from multiline string literals.
20+
21+
Dedented multiline strings use a new prefix "d" (shorthand for "dedent") before the opening quote of a multiline string literal.
22+
23+
.. code-block:: python
24+
25+
def hello_paragraph() -> str:
26+
return d"""
27+
<p>
28+
Hello, World!
29+
</p>
30+
"""
31+
32+
The closing triple quotes control how much indentation would be removed.
33+
In the above example, the returned string will contain three lines:
34+
35+
* ``" <p>\n"`` (four leading spaces)
36+
* ``" Hello, World!\n"`` (six leading spaces)
37+
* ``" </p>\n"`` (four leading spaces)
38+
39+
40+
Motivation
41+
==========
42+
43+
When writing multiline string literals within deeply indented Python code,
44+
users are faced with the following choices:
45+
46+
* Accept that the content of the string literal will be left-aligned.
47+
* Use multiple single-line string literals concatenated together instead of a multiline string literal.
48+
* Use ``textwrap.dedent()`` to remove indentation.
49+
50+
All of these options have drawbacks in terms of code readability and maintainability.
51+
52+
* Left-aligned multiline strings look awkward and tend to be avoided.
53+
In practice, many places including Python's own test code choose other methods.
54+
* Concatenated single-line string literals are more verbose and harder to maintain.
55+
* ``textwrap.dedent()`` is implemented in Python so it require some runtime overhead.
56+
It cannot be used in hot paths where performance is critical.
57+
58+
This PEP aims to provide a built-in syntax for dedented multiline strings that
59+
is both easy to read and write, while also being efficient at runtime.
60+
61+
62+
Rationale
63+
=========
64+
65+
The main alternative to this idea is to implement ``textwrap.dedent()`` in C
66+
and provide it as a ``str.dedent()`` method.
67+
This idea reduces the runtime overhead of ``textwrap.dedent()``.
68+
By making it a built-in method, it also allows for compile-time dedentation
69+
when called directly on string literals.
70+
71+
However, this approach has several drawbacks:
72+
73+
* To support cases where users want to include some indentation in the string,
74+
the ``dedent()`` method would need to accept an argument specifying
75+
the amount of indentation to remove.
76+
This would be cumbersome and error-prone for users.
77+
* When continuation lines (lines after line ends with a backslash) are used,
78+
they cannot be dedented.
79+
* f-strings may interpolate expressions as multiline string without indent.
80+
In such case, f-string + ``str.dedent()`` cannot dedent the whole string.
81+
* t-strings do not create str objects, so they cannot use the ``str.dedent()`` method.
82+
While adding a ``dedent()`` method to ``string.templatelib.Template`` is an option,
83+
it would lead to inconsistency since t-strings and f-strings are very similar but
84+
would have different behaviors regarding dedentation.
85+
86+
The ``str.dedent()`` method can still be useful for non-literal strings,
87+
so this PEP does not preclude that idea.
88+
However, for ease of use with multiline string literals, providing dedicated
89+
syntax is superior.
90+
91+
92+
Specification
93+
=============
94+
95+
Add a new string literal prefix "d" for dedented multiline strings.
96+
This prefix can be combined with "f", "t", and "r" prefixes.
97+
98+
This prefix is only for multiline string literals.
99+
So it can only be used with triple quotes (``"""`` or ``'''``).
100+
Using it with single or double quotes (``"`` or ``'``) is a syntax error.
101+
102+
Opening triple quotes needs to be followed by a newline character.
103+
This newline is not included in the resulting string.
104+
105+
The amount of indentation to be removed is determined by the whitespace
106+
(``' '`` or ``'\t'``) preceding the closing triple quotes.
107+
Mixing spaces and tabs in indentation raises a TabError, similar to Python's
108+
own indentation rules.
109+
110+
The dedentation process removes the determined amount of leading whitespace from each line in the string.
111+
Lines that are shorter than the determined indentation become just an empty line (e.g. ``"\n"``).
112+
Otherwise, if the line does not start with the determined indentation,
113+
Python raises an ``IndentationError``.
114+
115+
Unless combined with the "r" prefix, backslash escapes are processed after removing indentation.
116+
So you cannot use ``\\t`` to create indentation.
117+
And you can use line continuation (backslash at the end of line) and remove indentation from the continued line.
118+
119+
Examples:
120+
121+
.. code-block:: python
122+
123+
# whiltespace is shown as _ and TAB is shown as ---> for clarity.
124+
# Error messages are just for explanation. Actual messages may differ.
125+
126+
s = d"" # SyntaxError: d-string must be a multiline string
127+
s = d"""Hello""" # SyntaxError: d-string must be a multiline string
128+
s = d"""Hello
129+
__World!
130+
""" # SyntaxError: d-string must start with a newline
131+
132+
s = d"""
133+
__Hello
134+
__World!""" # SyntaxError: d-string must end with an indent-only line
135+
136+
s = d"""
137+
__Hello
138+
__World!
139+
""" # Zero indentation is removed because closing quotes are not indented.
140+
print(repr(s)) # '__Hello\n__World!\n'
141+
142+
s = d"""
143+
__Hello
144+
__World!
145+
_""" # One space indentation is removed.
146+
print(repr(s)) # '_Hello\n_World!\n'
147+
148+
s = d"""
149+
__Hello
150+
__World!
151+
__""" # Two spaces indentation are removed.
152+
print(repr(s)) # 'Hello\nWorld!\n'
153+
154+
s = d"""
155+
__Hello
156+
__World!
157+
___""" # IndentationError: missing valid indentation
158+
159+
s = d"""
160+
--->Hello
161+
__World!
162+
__""" # IndentationError: missing valid indentation
163+
164+
s = d"""
165+
--->--->__Hello
166+
--->--->__World!
167+
--->--->""" # TAB is allowed as indentation.
168+
# Spaces are just in the string, not indentation to be removed.
169+
print(repr(s)) # '__Hello\n__World!\n'
170+
171+
s = d"""
172+
--->____Hello
173+
--->____World!
174+
--->__""" # TabError: mixing spaces and tabs in indentation
175+
176+
s = d"""
177+
__Hello \
178+
__World!\
179+
__""" # line continuation works as ususal
180+
print(repr(s)) # 'Hello World!'
181+
182+
s = d"""\
183+
__Hello
184+
__World
185+
__""" # SyntaxError: d-string must starts with a newline.
186+
187+
s = dr"""
188+
Hello\
189+
World!\
190+
""" # d-string can be combined with r-string.
191+
print(repr(s)) # 'Hello\\\nWorld!\\\n'
192+
193+
s = df"""
194+
Hello, {"world".title()}!
195+
""" # d-string can be combined with f-string and t-string too.
196+
print(repr(s)) # 'Hello, World!\n'
197+
198+
s = dt"""
199+
Hello, {"world".title()}!
200+
"""
201+
print(type(s)) # <class 'string.templatelib.Template'>
202+
print(s.strings) # ('Hello, ', '!\n')
203+
print(s.values) # ('World',)
204+
print(s.interpolations) # (Interpolation('World', '"world".title()', None, ''),)
205+
206+
207+
How to Teach This
208+
=================
209+
210+
In the tutorial, we can introduce d-string with triple quote string literals.
211+
Additionally, we can add a note in the ``textwrap.dedent()`` documentation,
212+
providing a link to the d-string section in the language reference or the relevant part of the tutorial.
213+
214+
215+
Other Languages having Similar Features
216+
========================================
217+
218+
Java 15 introduced a feature called `Text Blocks <https://openjdk.org/jeps/378>`__.
219+
Since Java had not used triple qutes before, they introduced triple quotes for multiline string literals with automatic indent removal.
220+
221+
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>`__.
222+
223+
`Julia <https://docs.julialang.org/en/v1/manual/strings/#Triple-Quoted-String-Literals>`__ and
224+
`Swift <https://docs.swift.org/swift-book/documentation/the-swift-programming-language/stringsandcharacters/#Multiline-String-Literals>`__
225+
also support triple-quoted string literals that automatically remove indentation.
226+
227+
Java and Julia uses the least-indented line to determine the amount of indentation to be removed.
228+
Swift and C# uses the indentation of the closing triple quotes, similar to this PEP.
229+
230+
This PEP chose the Swift and C# approach because it is more simple and easy to explain.
231+
232+
233+
Reference Implementation
234+
========================
235+
236+
A CPython implementation of PEP 822 is `available <https://github.com/python/cpython/pull/143416>`__.
237+
238+
239+
Rejected Ideas
240+
==============
241+
242+
``str.dedent()`` method
243+
------------------------
244+
245+
As mentioned in the Rationale section, this PEP doesn't reject the idea of a ``str.dedent()`` method.
246+
A faster version of ``textwrap.dedent()`` implemented in C would be useful for runtime dedentation.
247+
248+
However, d-string is more suitable for multiline string literals because:
249+
250+
* It works well with f/t-strings.
251+
* It allows specifying the amount of indentation to be removed more easily.
252+
* It can dedent continuation lines.
253+
254+
255+
triple-backquote
256+
-----------------
257+
258+
It is considered that `using triple backquotes ("\`\`\`") <https://discuss.python.org/t/40679>`__
259+
for dedented multiline strings could be an alternative syntax.
260+
This notation is familiar to us from Markdown. While there were past concerns about certain keyboard layouts,
261+
nowadays many people are accustomed to typing this notation.
262+
263+
However, this notation conflicts when embedding Python code within Markdown or vice versa.
264+
Therefore, considering these drawbacks, increasing the variety of quote
265+
characters is not seen as a superior idea compared to adding a prefix to string literals.
266+
267+
268+
``__future__`` import
269+
---------------------
270+
Instead of adding a prefix to string literals, the idea of using a ``__future__``
271+
import to change the default behavior of multiline string literals was also considered.
272+
This could help simplify Python's grammar in the future.
273+
274+
But rewriting all existing complex codebases to the new notation may not be straightforward.
275+
Until all multiline strings in that source code are rewritten to the new notation, automatic dedentation cannot be utilized.
276+
277+
Until all users can rewrite existing codebases to the new notation, two types of Python syntax will coexist indefinitely.
278+
Therefore, `many people preferred the new string prefix <https://discuss.python.org/t/90988/54>`__ over the ``__future__`` import.
279+
280+
281+
Copyright
282+
=========
283+
284+
This document is placed in the public domain or under the
285+
CC0-1.0-Universal license, whichever is more permissive.

0 commit comments

Comments
 (0)