Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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.Templatededent() 메서드를 추가하는 방법도 있지만, 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을 도입했습니다.

JuliaSwift도 들여쓰기를 자동으로 제거하는 삼중 인용 문자열 리터럴을 지원합니다.

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-문자열 논의를 진전시키기 위해 거부되었지만, 여는 따옴표 바로 뒤에 아무것도 허용하지 않음으로써 향후 그 위치에 주석을 허용할 가능성을 남겨 둡니다.