PEP 822 – 들여쓰기가 제거된 여러 줄 문자열 (d-string)
- Author:
- Inada Naoki <songofacandy at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 05-Jan-2026
- Python-Version:
- 3.16
- Post-History:
- 05-Jan-2026
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 여러 줄 문자열 리터럴에서 들여쓰기를 자동으로 제거하는 기능의 추가를 제안합니다.
들여쓰기가 제거된 여러 줄 문자열은 여러 줄 문자열 리터럴의 여는 따옴표 앞에 새로운 접두사 “d”(“dedent”의 약자)를 붙입니다.
예제(공백은 . 기호로 표시합니다):
def hello_paragraph() -> str:
....return d"""
........<p>
..........Hello, World!
........</p>
...."""
textwrap.dedent()와 달리, 제거할 들여쓰기의 양을 결정할 때 닫는 따옴표 앞의 들여쓰기도 고려합니다. 따라서 위 예제에서 반환되는 문자열은 다음 세 줄로 구성됩니다.
"....<p>\n""......Hello, World!\n""....</p>\n"
동기
깊게 들여쓰기된 Python 코드 안에서 여러 줄 문자열 리터럴을 작성할 때, 사용자는 다음과 같은 선택지에 직면합니다:
- 문자열의 내용을 들여쓰기 없이 작성합니다.
- 여러 줄 문자열 리터럴 대신 여러 개의 한 줄 문자열 리터럴을 이어 붙여 사용합니다.
textwrap.dedent()를 사용하여 들여쓰기를 제거합니다.
이 선택지들은 모두 코드의 가독성과 유지 보수성 측면에서 단점이 있습니다.
- 깊게 들여쓰기된 코드에서 여러 줄 문자열을 들여쓰기 없이 작성하면 어색해 보이므로 기피되는 경향이 있습니다. 실제로 Python 자체의 테스트 코드를 포함한 많은 곳에서 다른 방법을 선택합니다.
- 이어 붙인 한 줄 문자열 리터럴은 더 장황하고 유지 보수하기도 더 어렵습니다. 각 줄 끝에
"\n"을 쓰는 것은 번거롭습니다. 인자 목록이나 컬렉션 리터럴에서는 여러 줄 문자열 리터럴 사이를 구분하는 쉼표를 빠뜨리기 쉽습니다. textwrap.dedent()는 Python으로 구현되어 있으므로 어느 정도의 런타임 오버헤드가 발생합니다. 게다가 t-문자열의 들여쓰기를 제거하는 데에는 사용할 수 없습니다.
이 PEP는 읽고 쓰기 쉬우면서도 런타임에 효율적인, 들여쓰기가 제거된 여러 줄 문자열을 위한 내장 문법을 제공하는 것을 목표로 합니다.
근거
이 아이디어의 주요 대안은 textwrap.dedent()를 C로 구현하여 str.dedent() 메서드로 제공하는 것입니다. 이 방법은 textwrap.dedent()의 런타임 오버헤드를 줄여 줍니다. 또한 내장 메서드로 만들면 문자열 리터럴에 직접 호출할 때 컴파일 시점에 들여쓰기를 제거할 수도 있습니다.
하지만 이 접근 방식에는 몇 가지 단점이 있습니다:
- 사용자가 문자열에 어느 정도의 들여쓰기를 포함하고 싶은 경우를 지원하려면,
dedent()메서드가 제거할 들여쓰기의 양을 지정하는 인자를 받아야 합니다. 이는 사용자에게 번거롭고 실수하기 쉬운 방식입니다. - 연속 줄(백슬래시로 끝나는 줄 다음에 오는 줄)을 사용하면 해당 줄의 들여쓰기는 제거할 수 없습니다.
- f-문자열에서는 표현식이 들여쓰기 없는 여러 줄 문자열로 보간될 수 있습니다. 이런 경우 f-문자열 +
str.dedent()로는 문자열 전체의 들여쓰기를 제거할 수 없습니다. - t-문자열은
str객체를 만들지 않으므로str.dedent()메서드를 사용할 수 없습니다.string.templatelib.Template에dedent()메서드를 추가하는 것도 하나의 선택지이지만, t-문자열과 f-문자열은 매우 비슷한데도 들여쓰기 제거에 관해서는 서로 다르게 동작하게 되므로 일관성이 떨어집니다.
str.dedent() 메서드는 리터럴 문자열이 아닌 문자열에는 여전히 유용할 수 있으므로, 이 PEP는 그 아이디어를 거부하지 않습니다. 그러나 여러 줄 문자열, 특히 t-문자열의 들여쓰기를 제거할 때는 전용 문법을 사용하는 편이 더 낫습니다.
명세
들여쓰기가 제거된 여러 줄 문자열을 위한 새 문자열 리터럴 접두사 “d”를 추가합니다. 이 접두사는 “f”, “t”, “r”, “b” 접두사와 함께 사용할 수 있습니다. 기존 문자열 접두사와 마찬가지로 대문자와 소문자는 같은 의미이며, 어떤 순서로든 쓸 수 있습니다.
이 접두사는 여러 줄 문자열 리터럴에만 사용합니다. 삼중 따옴표(""" 또는 ''')와 함께만 사용할 수 있습니다.
여는 삼중 따옴표 뒤에는 반드시 줄바꿈 문자가 와야 합니다. 이 줄바꿈은 결과 문자열에 포함되지 않습니다. d-문자열의 내용은 그다음 줄부터 시작합니다.
들여쓰기란 각 줄 앞부분에 있는 공백 문자(스페이스와 탭)입니다.
제거할 들여쓰기의 양은 문자열에 있는 줄들의 가장 긴 공통 들여쓰기로 결정됩니다. 공통 들여쓰기를 결정할 때 공백 문자로만 이루어진 줄은 무시합니다. 단, 닫는 삼중 따옴표가 있는 줄은 예외입니다.
스페이스와 탭은 서로 다른 문자로 취급합니다. 예를 들어 " hello"와 "\thello"에는 공통 들여쓰기가 없습니다.
들여쓰기 제거 과정에서는 문자열의 모든 줄에서 결정된 들여쓰기를 제거합니다.
- 길이가 결정된 들여쓰기보다 길거나 같은 줄은 결정된 들여쓰기로 시작해야 합니다. 그렇지 않으면 Python은
IndentationError를 발생시킵니다. 이러한 줄에서는 결정된 들여쓰기를 제거합니다. - 길이가 결정된 들여쓰기보다 짧은 줄(빈 줄 포함)은 결정된 들여쓰기의 접두사여야 합니다. 그렇지 않으면 Python은
IndentationError를 발생시킵니다. 이러한 줄은 빈 줄이 됩니다.
“r” 접두사와 함께 사용하지 않는 한, 백슬래시 이스케이프는 들여쓰기 제거 과정이 끝난 뒤에 처리됩니다. 따라서 들여쓰기에는 \\t를 사용할 수 없습니다. 또한 줄 이음(줄 끝의 백슬래시)을 사용하면서 이어지는 줄의 들여쓰기도 제거할 수 있습니다.
예시
# d-string must start with a newline.
s = d"" # SyntaxError: d-string must be triple-quoted
s = d"""""" # SyntaxError: d-string must start with a newline
s = d"""Hello""" # SyntaxError: d-string must start with a newline
s = 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 quotes 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 start 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',)
교육 방법
textwrap.dedent("""...""")와 d-문자열의 주요 차이점은 다음과 같이 설명할 수 있습니다:
textwrap.dedent()는 일반 함수이지만, d-문자열은 언어 문법의 일부입니다. d-문자열은 런타임 오버헤드가 없으며, t-문자열에서도 들여쓰기를 제거할 수 있습니다.textwrap.dedent()를 사용할 때는 첫 번째 줄바꿈 문자가 포함되지 않도록"""\로 시작해야 하지만, d-string에서는 문자열 내용이d"""다음 줄부터 시작하므로 백슬래시가 필요하지 않습니다.import textwrap s1 = textwrap.dedent("""\ Hello World! """) s2 = d""" Hello World! """ assert s1 == s2
textwrap.dedent()는 공통 들여쓰기를 결정할 때 모든 빈 줄을 무시하지만, d-문자열은 닫는 따옴표의 들여쓰기도 고려합니다. 이 덕분에 d-문자열은 필요할 때 결과에 일부 들여쓰기를 유지할 수 있습니다.import textwrap s1 = textwrap.dedent("""\ Hello World! """) s2 = d""" Hello World! """ assert s1 != s2 assert s1 == 'Hello\nWorld!\n' assert s2 == ' Hello\n World!\n'
- d-문자열은 이스케이프 시퀀스를 처리하기 전에 들여쓰기를 제거하므로, 줄 연속(줄 끝의 백슬래시)을 사용할 때 다음 줄의 들여쓰기도 제거될 수 있습니다.
import textwrap s1 = 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. """ assert s1 == s2
비슷한 기능을 가진 다른 언어
Java 15는 text blocks라는 기능을 도입했습니다. Java는 이전에 삼중 따옴표를 사용하지 않았기 때문에, 들여쓰기가 자동으로 제거되는 여러 줄 문자열 리터럴을 위해 삼중 따옴표를 도입했습니다.
C# 11도 raw string literals라는 비슷한 기능을 도입했습니다.
Julia와 Swift도 들여쓰기를 자동으로 제거하는 삼중 따옴표 문자열 리터럴을 지원합니다.
PHP 7.3은 Flexible Heredoc and Nowdoc Syntaxes를 도입했습니다. 삼중 따옴표 대신 닫는 마커(예: <<<END ... END)를 사용하지만, 이 역시 텍스트에서 들여쓰기를 제거합니다.
Ruby에도 heredoc의 각 줄에서 들여쓰기를 제거하는 “squiggly” heredoc이 있습니다.
Perl에도 Perl 5.26부터 “Indented Here-documents가 있습니다.
Java, Julia, Ruby는 들여쓰기가 가장 적은 줄을 기준으로 제거할 들여쓰기의 양을 결정합니다. Swift, C#, PHP, Perl은 닫는 삼중 따옴표나 닫는 마커의 들여쓰기를 사용합니다.
참조 구현
PEP 822의 CPython 구현은 methane/cpython#108에서 볼 수 있습니다.
기각된 아이디어
str.dedent() 메서드
근거(Rationale) 절에서 언급했듯이, 이 PEP는 str.dedent() 메서드라는 아이디어를 거부하지 않습니다. C로 구현된 더 빠른 버전의 textwrap.dedent()는 런타임에 들여쓰기를 제거하는 데 유용할 것입니다.
그러나 다음과 같은 이유로 여러 줄 문자열 리터럴에는 d-string이 더 적합합니다:
- f/t-문자열과도 잘 동작합니다.
- 제거할 들여쓰기의 양을 더 쉽게 지정할 수 있습니다.
- 연속된 줄의 들여쓰기도 제거할 수 있습니다.
삼중 백틱
들여쓰기가 제거된 여러 줄 문자열에 삼중 백틱을 사용하는 것이 대안 문법이 될 수 있다는 의견이 있습니다. 이 표기법은 Markdown을 통해 우리에게 익숙합니다. 과거에는 일부 키보드 배열에 대한 우려가 있었지만, 오늘날에는 많은 사람이 이 표기법을 입력하는 데 익숙합니다.
그러나 이 표기법은 Markdown 안에 Python 코드를 넣거나 그 반대의 경우에 충돌합니다. 따라서 이러한 단점을 고려할 때, 따옴표 문자의 종류를 늘리는 것은 문자열 리터럴에 접두사를 추가하는 것보다 나은 아이디어로 여겨지지 않습니다.
__future__ 임포트
문자열 리터럴에 접두사를 추가하는 대신, __future__ 임포트로 여러 줄 문자열 리터럴의 기본 동작을 바꾸는 아이디어도 검토되었습니다. 이는 향후 Python의 문법을 단순화하는 데 도움이 될 수 있습니다.
그러나 기존의 복잡한 코드베이스를 모두 새 표기법으로 다시 작성하는 일은 간단하지 않을 수 있습니다. 해당 소스 코드의 모든 여러 줄 문자열을 새 표기법으로 다시 작성하기 전까지는 자동 들여쓰기 제거를 활용할 수 없습니다.
모든 사용자가 기존 코드베이스를 새로운 표기법으로 다시 작성할 수 있을 때까지, 두 종류의 Python 문법이 무기한 공존하게 됩니다. 따라서 __future__ 임포트보다 새로운 문자열 접두사를 선호하는 사람이 많았습니다.
마지막 줄의 줄바꿈 제거
검토된 또 다른 아이디어는 마지막 줄의 줄바꿈 문자를 제거하는 것이었습니다. 이 아이디어는 Swift의 여러 줄 문자열 리터럴과 유사합니다.
이 아이디어를 따르면, 사용자는 아래와 같이 끝에 줄바꿈이 없으면서 들여쓰기가 있는 여러 줄 문자열을 작성할 수 있습니다:
s = d"""
Hello
World!
""" # " Hello\n World!" (no trailing newline)
s = d"""
Hello
World!
""" # " Hello\n World!\n" (has a trailing newline)
하지만 여러 줄 문자열 리터럴의 마지막 줄 끝에 줄바꿈을 포함하는 것은 매우 흔한 경우이며, 끝에 빈 줄을 요구하는 것은 Python의 전통적인 여러 줄 문자열 리터럴과 비교할 때 상당히 부자연스러워 보일 것입니다.
textwrap.dedent("""...""")를 사용할 때 많은 경우 사용자는 여는 따옴표 바로 뒤에 백슬래시를 써야 했고, 이는 불편한 일이었습니다. 따라서 d-문자열에서 여는 따옴표 뒤의 줄바꿈을 포함하지 않는 것은 사용자에게 분명한 개선입니다. 반면, 닫는 따옴표 앞 줄 끝의 줄바꿈을 제거하면 textwrap.dedent("""...""")를 사용하는 코드를 다시 작성할 때 혼란을 일으킬 가능성이 높습니다.
이 아이디어가 없더라도, 남는 들여쓰기는 필요 없지만 끝의 줄바꿈을 피하고 싶다면 닫는 따옴표를 마지막 내용 줄과 같은 줄에 둘 수 있습니다. 그리고 끝의 줄바꿈 없이 일부 들여쓰기를 유지해야 한다면, 줄 연속이나 str.rstrip() 같은 우회 방법을 사용할 수 있습니다.
s = d"""
Hello
World!"""
assert s == "Hello\nWorld!"
s = d"""
Hello
World!\
"""
assert s == " Hello\n World!"
s = dr"""
Hello
World!
""".rstrip()
assert s == " Hello\n World!"
이러한 우회 방법이 이상적이지는 않지만, 그 단점은 끝의 줄바꿈을 자동으로 제거할 때 생길 혼란보다 작다고 판단됩니다.
textwrap.dedent()는 닫는 따옴표의 들여쓰기를 고려하지 않으므로, textwrap.dedent()를 d-문자열로 다시 작성할 때는 이러한 우회 방법이 필요하지 않습니다.
여는 따옴표 뒤에 내용 허용
기존 """와 마찬가지로 여는 따옴표 바로 뒤에 내용을 쓸 수 있도록 허용하자는 제안도 있었습니다.
닫는 따옴표는 내용과 같은 줄에 올 수 있으므로, 여는 따옴표 바로 뒤에도 내용을 허용하는 것이 대칭적으로 보입니다. 또한 기존 """를 d-문자열로 다시 작성하기도 더 쉬워집니다.
반면 들여쓰기가 얼마나 제거될지 이해하기 위해 독자가 기억해야 하는 문법 규칙의 수가 늘어납니다.
또한 여는 따옴표 뒤에 내용을 허용하더라도, 기존 textwrap.dedent("""...""") 사용자가 첫 번째 줄바꿈이 포함되지 않도록 """\<newline>를 작성해야 하는 사용성 문제는 해결되지 않습니다. Julia는 """<newline>으로 시작할 때 첫 번째 줄바꿈을 제거하여 이 문제를 피하지만, 이는 사용자가 기억해야 할 문법 규칙을 하나 더 추가합니다.
__future__ 임포트를 사용하는 아이디어를 기각했으므로, 우리는 기존 여러 줄 문자열 리터럴 사용자가 d-문자열로 쉽게 다시 작성하는 것보다 textwrap.dedent()에서 옮겨 오는 사용자나 textwrap.dedent()를 쓰고 싶었지만 어떤 이유로 쓸 수 없었던 사용자의 사용 편의성을 우선했습니다.
또한 과거 논의에서, 여는 따옴표 바로 뒤에 문법 강조를 위한 힌트나 주석을 작성할 수 있도록 허용하자는 제안이 있었습니다. 그 제안은 d-문자열 논의를 진전시키기 위해 기각되었지만, 여는 따옴표 바로 뒤에 아무것도 허용하지 않음으로써 향후 그 자리에 주석을 허용할 여지를 남겨 둡니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.