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-string의 주요 차이점은 다음과 같이 설명할 수 있습니다:
textwrap.dedent()는 일반 함수이지만, d-string은 언어 구문의 일부입니다. d-string에는 런타임 오버헤드가 없으며, t-string에서도 들여쓰기를 제거할 수 있습니다.textwrap.dedent()를 사용할 때는 첫 번째 줄 바꿈 문자가 포함되지 않도록"""\로 시작해야 하지만, d-string에서는 문자열 내용이d"""다음 줄부터 시작하므로 백슬래시가 필요하지 않습니다.import textwrap s1 = textwrap.dedent("""\ Hello World! """) s2 = d""" Hello World! """ assert s1 == s2
textwrap.dedent()는 공통 들여쓰기를 결정할 때 모든 빈 줄을 무시하지만, d-string은 닫는 따옴표의 들여쓰기도 고려합니다. 따라서 필요한 경우 d-string은 결과에서 일부 들여쓰기를 유지할 수 있습니다.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-string은 이스케이프 시퀀스를 처리하기 전에 들여쓰기를 제거하므로, 줄 연속(줄 끝의 백슬래시)을 사용할 때 다음 줄의 들여쓰기도 제거할 수 있습니다.
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() 메서드
근거 섹션에서 언급했듯이, 이 PEP는 str.dedent()메서드라는 아이디어를 거부하지 않습니다. C로 구현된 더 빠른 textwrap.dedent()버전은 런타임 들여쓰기 제거에 유용할 것입니다.
그러나 d-string은 다음과 같은 이유로 여러 줄 문자열 리터럴에 더 적합합니다:
- f/t-string과 잘 작동합니다.
- 제거할 들여쓰기의 양을 더 쉽게 지정할 수 있습니다.
- 연속 줄의 들여쓰기를 제거할 수 있습니다.
삼중 백틱
들여쓰기가 제거된 여러 줄 문자열을 위해 using triple backticks를 대체 구문으로 사용할 수 있다고 여겨집니다. 이 표기법은 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__임포트를 사용하는 아이디어를 거부했으므로, textwrap.dedent()에서 다시 작성하는 사용자나 어떤 이유로 textwrap.dedent()를 사용할 수 없었지만 사용하고 싶었던 사용자에게 기존 여러 줄 문자열 리터럴 사용자가 d-문자열로 다시 작성하는 편의성보다 사용 편의성을 우선했습니다.
추가로, 과거 논의에서는 여는 따옴표 바로 뒤에 구문 강조를 위한 힌트나 주석을 작성할 수 있도록 하자는 제안이 있었습니다. 그 제안은 d-문자열 논의를 진전시키기 위해 거부되었지만, 여는 따옴표 바로 뒤에 아무것도 허용하지 않음으로써 향후 그 위치에 주석을 허용할 가능성을 남겨 둡니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.