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