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

Python 개선 제안 한국어 번역

PEP 701 – f-문자열의 구문 형식화

Author:
Pablo Galindo Salgado <pablogsal at python.org>, Batuhan Taskaya <batuhan at python.org>, Lysandros Nikolaou <lisandrosnik at gmail.com>, Marta Gómez Macías <cyberwitch at google.com>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Created:
15-Nov-2022
Python-Version:
3.12
Post-History:
19-Dec-2022
Resolution:
14-Mar-2023

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at f-strings.

×

See PEP 1 for how to propose changes.

초록

이 문서는 PEP 498에서 원래 규정한 일부 제한을 완화하고, f-문자열을 파서에 직접 통합할 수 있는 형식화된 문법을 제공할 것을 제안합니다. 제안된 f-문자열의 구문 형식화는 f-문자열의 구문 분석 및 해석 방식에 약간의 부수 효과를 가져오지만, 최종 사용자와 라이브러리 개발자에게 상당한 이점을 제공하는 동시에 f-문자열 구문 분석 전용 코드의 유지 보수 비용을 크게 줄입니다.

동기

f-문자열이 PEP 498에서 처음 도입되었을 때, f-문자열에 대한 형식 문법은 제공되지 않았습니다. 또한 기존 렉서를 수정하지 않고 CPython에 f-문자열 구문 분석을 구현할 수 있도록 몇 가지 제한이 명세에 포함되었습니다. 이러한 제한은 이전부터 인식되어 왔으며 PEP 536에서 이를 완화하려는 시도가 있었지만, none of this work was ever implemented. 이러한 제한 중 일부(PEP 536에서 처음 정리됨)는 다음과 같습니다.

  1. f-문자열을 구분하는 따옴표 문자를 표현식 부분 안에서 사용할 수 없습니다.:
    >>> f'Magic wand: { bag['wand'] }'
                                 ^
    SyntaxError: invalid syntax
    
  2. 이전에 고려된 우회 방법은 실행되는 코드에 이스케이프 시퀀스를 포함하게 되므로 f-문자열에서 금지됩니다.:
    >>> f'Magic wand { bag[\'wand\'] } string'
    SyntaxError: f-string expression portion cannot include a backslash
    
  3. 여러 줄 f-문자열에서도 주석은 허용되지 않습니다.:
    >>> f'''A complex trick: {
    ... bag['bag']  # recursive bags!
    ... }'''
    SyntaxError: f-string expression part cannot include '#'
    
  4. 이스케이프 시퀀스를 확장하지 않고 표현식을 임의로 중첩하는 기능은 단순히 변수 이름이 아니라 표현식을 사용하는 문자열 보간 방식을 채택한 다른 많은 언어에서 제공됩니다. 몇 가지 예는 다음과 같습니다.
    # Ruby
    "#{ "#{1+2}" }"
    
    # JavaScript
    `${`${1+2}`}`
    
    # Swift
    "\("\(1+2)")"
    
    # C#
    $"{$"{1+2}"}"
    

이러한 제한은 언어 사용자 관점에서 아무런 목적이 없으며, f-문자열 리터럴에 예외 없는 정규 문법을 부여하고 전용 구문 분석 코드로 이를 구현하면 완화할 수 있습니다.

f-문자열의 또 다른 문제는 CPython의 현재 구현이 f-문자열을 STRING토큰으로 토큰화한 다음 이러한 토큰을 후처리하는 방식에 의존한다는 점입니다. 여기에는 다음과 같은 문제가 있습니다.

  1. CPython 파서의 유지 보수 비용이 상당히 증가합니다. 이는 구문 분석 코드를 수작업으로 작성해야 하기 때문이며, 역사적으로 이로 인해 상당한 수의 불일치와 버그가 발생했습니다. C에서 구문 분석 코드를 수작업으로 작성하고 유지 보수하는 일은 원래 렉서 버퍼를 대상으로 많은 수동 메모리 관리를 처리해야 하므로 항상 오류가 발생하기 쉽고 위험한 것으로 여겨져 왔습니다.
  2. f-문자열 구문 분석 코드는 PEP 617에서 처음 도입된 새로운 PEG 파서가 가능하게 한 개선된 오류 메시지 메커니즘을 사용할 수 없습니다. 이러한 오류 메시지가 가져온 개선 사항은 크게 환영받았지만, 안타깝게도 f-문자열은 구문 분석 장치의 별도 부분에서 구문 분석되므로 그 혜택을 받을 수 없습니다. 이는 특히 안타까운 일인데, 표현식 부분 내부에서 발생하는 서로 다른 암시적 토큰화로 인해 혼동을 일으킬 수 있는 f-문자열의 구문 기능이 여러 가지 있기 때문입니다(예를 들어 f"{y:=3}"은 대입 표현식이 아닙니다).
  3. 다른 Python 구현은 f-문자열을 올바르게 구현했는지 확인할 방법이 없습니다. 다른 언어 기능과 달리 f-문자열은 official Python grammar의 일부가 아니기 때문입니다. 이는 중요한데, 여러 주요 대체 구현이 CPython의 PEG 파서를 사용하고 있으며(such as PyPy와 같이), 공식 PEG 문법을 기반으로 문법을 작성하고 있기도 하기 때문입니다. f-문자열이 별도의 파서를 사용한다는 사실은 이러한 대체 구현이 공식 문법을 활용하고 해당 문법에서 파생된 오류 메시지 개선의 혜택을 받지 못하게 합니다.

이 제안의 한 버전은 처음에 discussed on Python-Dev되었고 presented at the Python Language Summit 2022되었으며, 그곳에서 열렬한 환영을 받았습니다.

근거

이 PEP는 새로운 Python PEG 파서(PEP 617에 기반하여 “f-문자열”을 재정의할 것을 제안하며, 특히 문자열 구성 요소와 표현식(또는 대체, {...}) 구성 요소를 명확히 분리하는 데 중점을 둡니다. PEP 498은 “f-문자열”의 구문 부분을 다음과 같이 요약합니다.

Python 소스 코드에서 f-문자열은 ‘f’가 앞에 붙고 중괄호 안에 표현식을 포함하는 리터럴 문자열입니다. 표현식은 해당 값으로 대체됩니다.

그러나 PEP 498도 표현식 구성 요소 내부에 포함할 수 있거나 포함할 수 없는 항목에 대한 공식적인 제외 목록을 포함하고 있었습니다(주로 기존 파서의 제한 때문입니다). 공식 문법을 명확히 확립함으로써, 이제 구현 세부 사항으로 인해 부과된 제한에 얽매이지 않고 f-문자열의 표현식 구성 요소를 진정으로 “해당 컨텍스트에서 적용 가능한 모든 Python 표현식”으로 정의할 수 있게 되었습니다.

이러한 공식화 작업과 위의 전제는 모호한 제한을 단순화하고 제거할 수 있으므로 Python 프로그래머에게도 상당한 이점을 제공합니다. 이는 f-문자열 리터럴(그리고 일반적으로 Python 언어)의 정신적 부담과 인지적 복잡성을 줄입니다.

  1. 표현식 구성 요소에는 일반 Python 표현식에 포함할 수 있는 모든 문자열 리터럴을 포함할 수 있습니다. 이를 통해 f-문자열의 표현식 구성 요소 내부에 동일한 따옴표 유형(및 길이)을 사용하는 문자열 리터럴(형식화 여부와 관계없이)을 중첩할 수 있게 됩니다.:
    >>> f"These are the things: {", ".join(things)}"
    
    >>> f"{source.removesuffix(".py")}.c: $(srcdir)/{source}"
    
    >>> f"{f"{f"infinite"}"}" + " " + f"{f"nesting!!!"}"
    

    이 “기능”이 바람직하다는 데에는 보편적인 합의가 없으며, 일부 사용자는 이를 읽기 어렵다고 생각합니다. 이에 대한 다양한 견해에 관한 논의는 Considerations regarding quote reuse 절을 참조하십시오.

  2. 대부분의 사람에게 직관에 어긋난다고 느껴진 또 다른 문제는 f-문자열의 표현식 구성 요소 내부에서 백슬래시를 지원하지 않는다는 점입니다. 반복해서 제기되는 한 가지 예는 컨테이너를 결합하기 위한 표현식 부분에 개행 문자를 포함하는 것입니다. 예를 들면 다음과 같습니다.:
    >>> a = ["hello", "world"]
    >>> f"{'\n'.join(a)}"
    File "<stdin>", line 1
        f"{'\n'.join(a)}"
                        ^
    SyntaxError: f-string expression part cannot include a backslash
    

    이 문제에 대한 일반적인 우회 방법은 개행을 중간 변수에 할당하거나 f-문자열을 만들기 전에 전체 문자열을 미리 생성하는 것이었습니다.:

    >>> a = ["hello", "world"]
    >>> joined = '\n'.join(a)
    >>> f"{joined}"
    'hello\nworld'
    

    이제 새로운 PEG 파서가 백슬래시를 쉽게 지원할 수 있으므로 표현식 부분에서 백슬래시를 허용하는 것이 자연스럽게 느껴집니다.

    >>> a = ["hello", "world"]
    >>> f"{'\n'.join(a)}"
    'hello\nworld'
    
  3. 이 문서에서 제안하는 변경 사항 이전에는 f-문자열을 중첩할 수 있는 방식에 명시적인 제한이 없었지만, f-문자열의 표현식 구성 요소 내부에서 문자열 따옴표를 재사용할 수 없었기 때문에 f-문자열을 임의로 중첩하는 것은 불가능했습니다. 실제로 다음이 작성할 수 있는 가장 깊은 중첩 f-문자열입니다.:
    >>> f"""{f'''{f'{f"{1+1}"}'}'''}"""
    '2'
    

    이 PEP를 통해 f-문자열의 표현식 구성 요소 내부에 모든 유효한 Python 표현식을 배치할 수 있으므로 이제 따옴표를 재사용할 수 있으며, 따라서 f-문자열을 임의로 중첩할 수 있습니다.:

    >>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
    '2'
    

    이는 임의의 표현식을 허용한 결과일 뿐이지만, 이 PEP의 작성자들은 이것이 근본적인 이점이라고 생각하지 않으며 이러한 중첩이 임의로 가능하다고 언어 명세에서 명시적으로 의무화하지 않기로 결정했습니다. 임의로 깊은 중첩을 허용하면 렉서 구현이 훨씬 복잡해지기 때문입니다(특히 렉서/파서 파이프라인은 ‘f-문자열 디버깅 표현식’을 지원하기 위해 “토큰화 해제”를 허용해야 하며, 임의의 중첩이 허용되면 이 작업에 특히 큰 부담이 발생합니다). 따라서 구현은 필요한 경우 중첩 깊이에 제한을 자유롭게 둘 수 있습니다. 이는 드문 상황이 아닙니다. CPython 구현은 이미 괄호와 대괄호의 중첩 깊이 제한, 블록 중첩 제한, if문에 있는 분기 수 제한, 별표 언패킹의 표현식 수 제한 등을 비롯해 곳곳에 여러 제한을 두고 있습니다.

명세

f-문자열에 대해 공식적으로 제안된 PEG 문법 명세는 다음과 같습니다(문법에 관한 자세한 내용은 PEP 617을 참조하십시오).

fstring
    | FSTRING_START fstring_middle* FSTRING_END
fstring_middle
    | fstring_replacement_field
    | FSTRING_MIDDLE
fstring_replacement_field
    | '{' (yield_expr | star_expressions) "="? [ "!" NAME ] [ ':' fstring_format_spec* ] '}'
fstring_format_spec:
    | FSTRING_MIDDLE
    | fstring_replacement_field

새로운 토큰(FSTRING_START, FSTRING_MIDDLE, FSTRING_END)은 이 문서의 뒷부분 에 정의되어 있습니다.

이 PEP는 허용되는 f-문자열 중첩 수준(다른 f-문자열의 표현식 부분 안에 있는 f-문자열)을 구현에 맡기지만, 중첩 수준의 하한을 5단계로 명시합니다. 이는 사용자가 “합리적인” 깊이로 f-문자열을 중첩할 수 있으리라는 합리적인 기대를 가질 수 있도록 하기 위한 것입니다. 이 PEP는 중첩 제한이 언어 명세의 일부가 아님을 의미하지만, 언어 명세가 임의의 중첩을 의무화하지도 않음을 의미합니다.

마찬가지로 이 PEP는 형식 지정자의 표현식 중첩 수준을 구현에 맡기지만, 중첩 수준의 하한을 2단계로 명시합니다. 이는 다음이 항상 유효해야 함을 의미합니다.

f"{'':*^{1:{1}}}"

하지만 다음은 구현에 따라 유효할 수도 있고 유효하지 않을 수도 있습니다.

f"{'':*^{1:{1:{1}}}}"

새로운 문법은 현재 구현의 추상 구문 트리(AST)를 보존합니다. 이는 이 PEP가 f-문자열을 사용하는 기존 코드에 어떠한 의미 변경도 도입하지 않음을 의미합니다.

f-문자열 디버그 표현식 처리

Python 3.8부터 = 연산자를 사용하여 표현식을 디버깅하는 데 f-문자열을 사용할 수 있습니다. 예를 들어:

>>> a = 1
>>> f"{1+1=}"
'1+1=2'

이 의미론은 PEP에서 공식적으로 도입되지 않았으며, 현재 문자열 파서에서 특수한 경우로 구현되어 bpo-36817에서 문서화되었고, f-문자열 어휘 분석 섹션에 설명되어 있습니다.

이 기능은 이 PEP에서 제안된 변경 사항의 영향을 받지 않지만, 이 기능을 공식적으로 처리하려면 렉서가 f-문자열의 표현식 부분을 “untokenize”할 수 있어야 한다는 점을 명시하는 것이 중요합니다. 현재 문자열 파서는 문자열 토큰의 내용에 직접 연산할 수 있으므로 이는 문제가 되지 않습니다. 그러나 이 기능을 특정 파서 구현에 통합하려면 렉서가 f-문자열 표현식 부분의 원시 문자열 내용을 추적하고, f-문자열 노드의 구문 분석 트리를 구성할 때 이를 파서에서 사용할 수 있도록 해야 합니다. 현재 지정된 방식으로는 f-문자열 디버그 표현식이 {= 문자 뒤의 공백을 포함하여 표현식 내의 공백을 보존하므로, 단순한 “untokenization”만으로는 충분하지 않습니다. 즉, f-문자열 표현식 부분의 원시 문자열 내용은 관련 토큰만이 아니라 온전한 상태로 유지되어야 합니다.

파서/렉서 구현에서 이 문제를 처리하는 방법은 물론 구현에 달려 있습니다.

새 토큰

다음 세 가지 새 토큰이 도입됩니다: FSTRING_START, FSTRING_MIDDLEFSTRING_END. 특정 구현의 맥락에 따라 서로 다른 렉서가 여기에서 제안된 것보다 더 효율적인 서로 다른 구현을 사용할 수 있습니다. 그러나 다음 정의는 CPython의 공개 API(예: tokenize 모듈)의 일부로 사용되며, 독자가 제안된 문법 변경 사항과 토큰 사용 방법을 더 잘 이해할 수 있도록 참고 자료로도 제공됩니다.

  • FSTRING_START: 이 토큰에는 f-문자열 접두사(f/F/fr)와 여는 따옴표가 포함됩니다.
  • FSTRING_MIDDLE: 이 토큰에는 표현식 부분에 속하지 않으며 여는 중괄호나 닫는 중괄호도 아닌 문자열 내부 텍스트의 일부가 포함됩니다. 여기에는 여는 따옴표와 첫 번째 표현식 중괄호({) 사이의 텍스트, 두 표현식 중괄호(}{) 사이의 텍스트, 마지막 표현식 중괄호(})와 닫는 따옴표 사이의 텍스트가 포함될 수 있습니다.
  • FSTRING_END: 이 토큰에는 닫는 따옴표가 포함됩니다.

이러한 토큰은 항상 문자열 부분이며, 지정된 제한 사항이 적용된 STRING토큰과 의미상 동일합니다. 렉서는 f-문자열을 어휘 분석할 때 이러한 토큰을 생성해야 합니다. 즉, 이제 토큰화기는 f-문자열에 대해 단일 토큰을 생성할 수 없습니다. 렉서가 이 토큰을 방출하는 방식은 지정되지 않습니다. 이는 각 구현에 크게 의존하기 때문입니다(표준 라이브러리의 Python 버전 렉서조차 PEG 파서에서 사용하는 렉서와 다르게 구현되어 있습니다).

예를 들어:

f'some words {a+b:.3f} more words {c+d=} final words'

다음과 같이 토큰화됩니다.:

FSTRING_START - "f'"
FSTRING_MIDDLE - 'some words '
LBRACE - '{'
NAME - 'a'
PLUS - '+'
NAME - 'b'
OP - ':'
FSTRING_MIDDLE - '.3f'
RBRACE - '}'
FSTRING_MIDDLE - ' more words '
LBRACE - '{'
NAME - 'c'
PLUS - '+'
NAME - 'd'
OP - '='
RBRACE - '}'
FSTRING_MIDDLE - ' final words'
FSTRING_END - "'"

반면 f"""some words"""는 단순히 다음과 같이 토큰화됩니다.:

FSTRING_START - 'f"""'
FSTRING_MIDDLE - 'some words'
FSTRING_END - '"""'

tokenize 모듈의 변경 사항

해당 tokenize 모듈은 f-문자열을 구문 분석할 때 이전 절에서 설명한 대로 이러한 토큰을 방출하도록 조정하여, 도구가 이 새로운 토큰화 스키마를 활용하고 자체 f-문자열 토크나이저와 파서를 구현하지 않아도 되도록 합니다.

이러한 새 토큰을 생성하는 방법

기존 렉서를 조정하여 이러한 토큰을 방출하는 한 가지 방법은 “렉서 모드” 스택을 통합하거나 서로 다른 렉서의 스택을 사용하는 것입니다. 이는 f-문자열 시작 토큰을 만났을 때 렉서가 “일반 Python 어휘 분석”에서 “f-문자열 어휘 분석”으로 전환해야 하고, f-문자열은 중첩될 수 있으므로 f-문자열이 닫힐 때까지 컨텍스트를 보존해야 하기 때문입니다. 또한 f-문자열 표현식 부분 내부의 “렉서 모드”는 일반 Python 렉서의 “상위 집합”처럼 동작해야 합니다. 표현식 부분의 종결자인 }를 만났을 때 f-문자열 어휘 분석으로 다시 전환할 수 있어야 하며, f-문자열 형식 지정과 디버그 표현식도 처리해야 하기 때문입니다. 참고로, 다음은 CPython과 유사한 토큰화기를 수정하여 이러한 새 토큰을 방출하기 위한 알고리즘 초안입니다:

  1. 렉서가 f-string이 시작되는 것을 감지하면(문자 ‘f/F’와 가능한 따옴표 중 하나를 감지하여) 유효한 따옴표(", """, ' 또는 ''' 중 하나)가 감지될 때까지 계속 진행하고, 캡처한 내용(‘f/F’와 시작 따옴표)을 담은 FSTRING_START 토큰을 방출합니다. 토크나이저 모드 스택에 “F-string 토큰화”를 위한 새 토크나이저 모드를 추가합니다. 2단계로 이동합니다.
  2. 다음 중 하나가 나타날 때까지 토큰을 계속 소비하십시오:
    • 여는 따옴표와 동일한 닫는 따옴표입니다.
    • “형식 지정자 모드”(3단계 참조)인 경우, 여는 중괄호({), 닫는 중괄호(}) 또는 줄 바꿈 토큰(\n)입니다.
    • “형식 지정자 모드”(3단계 참조)가 아닌 경우, 다른 여는 중괄호나 닫는 중괄호가 바로 뒤따르지 않는 여는 중괄호({) 또는 닫는 중괄호(})입니다.

    모든 경우에 문자 버퍼가 비어 있지 않으면, 지금까지 캡처한 내용을 담은 FSTRING_MIDDLE 토큰을 방출하되, 이중 여는 중괄호와 닫는 중괄호를 각각 단일 여는 중괄호와 닫는 중괄호로 변환합니다. 이제 감지된 문자에 따라 다음과 같이 진행합니다:

    • 여는 따옴표와 일치하는 닫는 따옴표가 감지되면 4단계로 이동합니다.
    • 다른 여는 괄호가 바로 뒤따르지 않는 여는 괄호가 감지되면 3단계로 이동합니다.
    • 다른 닫는 괄호가 바로 뒤따르지 않는 닫는 괄호가 감지되면 닫는 괄호에 대한 토큰을 방출하고 2단계로 이동합니다.
  3. 토크나이저 모드 스택에 “f-string 내부의 일반 Python 토큰화”를 위한 새 토크나이저 모드를 추가하고, 이를 사용하여 토큰화를 진행합니다. 이 모드는 f-string 부분에 진입할 때 추가된 여는 괄호 토큰과 동일한 중첩 수준에서 : 또는 } 문자가 감지될 때까지 “일반 Python 토큰화”로 토큰화합니다. 이 모드를 사용하여 중지 지점 중 하나에 도달할 때까지 토큰을 방출합니다. 이 시점에 도달하면 감지된 중지 문자에 해당하는 토큰을 방출하고, 토크나이저 모드 스택에서 현재 토크나이저 모드를 제거한 다음 2단계로 이동합니다. 중지 지점이 : 문자인 경우 “형식 지정자 모드”에서 2단계로 진입합니다.
  4. 캡처한 내용을 담은 FSTRING_END 토큰을 방출하고, 현재 토크나이저 모드(“F-string 토큰화”에 해당)를 제거한 다음 “일반 Python 모드”로 돌아갑니다.

물론 앞서 언급했듯이, 임의의 토크나이저에서 이를 수행하는 방법을 정확하게 명세할 수는 없습니다. 변경할 렉서의 구체적인 구현과 특성에 따라 달라지기 때문입니다.

새 문법의 결과입니다.

아래에서 설명하듯이 PEP에서 언급된 모든 제한이 f-string 리터럴에서 해제됩니다.

  • 이제 표현식 부분에 f-string 리터럴을 구분하는 데 사용된 것과 동일한 종류의 따옴표로 구분된 문자열을 포함할 수 있습니다.
  • 이제 Python 코드의 다른 곳에서와 마찬가지로 표현식 내부에 백슬래시가 나타날 수 있습니다. f-string 리터럴 안에 중첩된 문자열이 있는 경우, 가장 안쪽 문자열이 평가될 때 이스케이프 시퀀스가 확장됩니다.
  • 이제 표현식 괄호 내부에서 줄 바꿈이 허용됩니다. 이는 이제 다음과 같은 항목이 허용됨을 의미합니다.:
    >>> x = 1
    >>> f"___{
    ...     x
    ... }___"
    '___1___'
    
    >>> f"___{(
    ...     x
    ... )}___"
    '___1___'
    
  • # 문자를 사용하는 주석을 f-string의 표현식 부분 안에 작성할 수 있습니다. 표현식 부분의 닫는 괄호(})는 주석이 있는 줄과 다른 줄에 있어야 하며, 그렇지 않으면 주석의 일부로 무시된다는 점에 유의하십시오.

따옴표 재사용과 관련된 고려 사항입니다.

여기서 제안하는 문법의 결과 중 하나는 위에서 언급했듯이 f-string 표현식에 외부 f-string 리터럴을 구분하는 데 사용된 것과 동일한 종류의 따옴표로 구분된 문자열을 포함할 수 있다는 점입니다. 예를 들면 다음과 같습니다:

>>> f" something { my_dict["key"] } something else "

이 PEP의 discussion thread for this PEP에서 이 측면에 관한 여러 우려가 제기되었으며, 이 PEP를 수락하거나 거부할 때 이를 고려해야 하므로 여기에서 해당 우려를 모아 보려고 합니다.

이러한 반론에는 다음과 같은 것들이 포함됩니다:

  • 많은 사람은 동일한 문자열 안에서 인용부호를 재사용하는 것이 혼란스럽고 읽기 어렵다고 생각합니다. 이는 인용부호 재사용을 허용하면 현재 Python의 특성, 즉 문자열이 동일한 종류의 인용부호 두 쌍이 연속으로 나타나 완전히 구분된다는 사실을 위반하기 때문이며, 이 자체는 매우 단순한 규칙입니다. 인용부호 재사용이 사람이 해석하기 더 어려워져 가독성이 낮은 코드로 이어질 수 있는 이유 중 하나는 다른 구분자와 달리 시작과 끝에 같은 인용부호 문자를 사용하기 때문입니다.
  • 일부 사용자는 정규 표현식이나 단순한 구분자 일치 도구처럼 문자열과 f-문자열을 감지하기 위해 간단한 메커니즘에 의존하는 일부 렉서 및 구문 강조 도구가 인용부호 재사용으로 인해 중단될 수 있다는 우려를 제기했습니다. f-문자열에 인용부호 재사용을 도입하면 이러한 도구가 계속 작동하도록 유지하기가 더 까다로워지거나 도구가 완전히 중단될 수 있습니다(예를 들어 정규 표현식은 구분자가 있는 임의의 중첩 구조를 구문 분석할 수 없습니다). 표준 라이브러리에 포함된 IDLE 편집기는 f-문자열에 구문 강조를 올바르게 적용하려면 일부 작업이 필요할 수 있는 도구의 한 예입니다.

다음은 찬성 논거 중 일부입니다:

  • 유사한 구문 구조(일반적으로 “문자열 보간”이라고 합니다)를 허용하는 많은 언어는 인용부호 재사용과 임의의 중첩을 허용합니다. 이러한 언어에는 JavaScript, Ruby, C#, Bash, Swift 및 그 밖의 많은 언어가 포함됩니다. 많은 언어가 인용부호 재사용을 허용한다는 사실은 Python에서 이를 허용하는 데 찬성하는 설득력 있는 논거가 될 수 있습니다. 이는 다른 언어를 사용해 온 사용자에게 해당 언어를 더 익숙하게 느끼게 하기 때문입니다.
  • 다른 많은 인기 언어가 문자열 보간 구조에서 인용부호 재사용을 허용하므로, 이러한 언어의 구문 강조를 지원하는 편집기에는 Python에서 인용부호를 재사용한 f-문자열의 구문 강조를 지원하는 데 필요한 도구가 이미 갖춰져 있을 것입니다. 이는 Python의 구문 강조를 처리하는 파일을 이 새로운 기능을 지원하도록 업데이트해야 하더라도, 그 작업이 불가능하거나 매우 어렵지는 않을 것으로 예상된다는 의미입니다.
  • 인용부호 재사용을 허용하는 한 가지 장점은 다른 구문과 깔끔하게 결합된다는 것입니다. 이를 때때로 “참조 투명성”이라고 합니다. 예를 들어 f(x+1)이 있고 a가 완전히 새로운 변수라고 가정하면, 이는 a = x+1; f(a)와 동일하게 동작해야 합니다. 그 역도 마찬가지입니다. 따라서 다음과 같은 경우를 생각해 보겠습니다.:
    def py2c(source):
        prefix = source.removesuffix(".py")
        return f"{prefix}.c"
    

    변수 prefix를 그 정의로 치환하더라도 결과는 같아야 합니다:

    def py2c(source):
        return f"{source.removesuffix(".py")}.c"
    
  • 현재 형태의 코드 생성기(표준 라이브러리의 ast.unparse와 같은)는 f-문자열 안의 표현식이 사용되는 컨텍스트에 적절하게 맞도록 보장하기 위해 복잡한 알고리즘에 의존합니다. 이러한 복잡한 알고리즘에는 바깥쪽 인용부호를 추적하여 사용되지 않은 인용부호 유형을 찾고, 가능하면 백슬래시를 포함하지 않는 문자열 표현을 생성해야 하는 등의 어려움이 따릅니다. 인용부호와 백슬래시의 재사용을 허용하면 f-문자열을 처리하는 코드 생성기가 상당히 단순해집니다. 일반적인 Python 표현식 논리를 f-문자열 안팎에서 특별한 처리 없이 사용할 수 있기 때문입니다.
  • 인용부호 재사용을 제한하면 제안된 변경 사항의 구현 복잡성이 상당히 증가합니다. 이는 파서가 특정 인용부호를 사용하는 f-문자열의 표현식 부분을 구문 분석하고 있는 컨텍스트를 갖도록 강제하여, 해당 인용부호를 재사용하는 표현식을 거부해야 하는지 알아내야 하기 때문입니다. 임의로 백트래킹할 수 있는 파서(PEG 파서 등)에서 이 컨텍스트를 계속 전달하는 일은 간단하지 않습니다. f-문자열이 임의의 깊이로 중첩될 수 있다는 점을 고려하면 문제가 더욱 복잡해지며, 따라서 여러 인용부호 유형을 거부해야 할 수도 있습니다.

    커뮤니티의 의견을 수집하기 위해, PEP의 이 측면에 대해 커뮤니티가 어떻게 생각하는지 파악할 수 있도록 a poll이 시작되었습니다.

하위 호환성

이 PEP는 Python 언어에 하위 호환성을 해치는 구문적 또는 의미적 변경 사항을 도입하지 않습니다. 그러나 새로운 f-문자열 토큰을 지원하려면 표준 라이브러리의 준공개 부분인 tokenize 모듈을 업데이트해야 합니다(f-문자열을 올바르게 토큰화할 수 있도록 도구 작성자를 지원하기 위해서입니다). 공개 API인 tokenize가 어떤 영향을 받는지에 관한 자세한 내용은 Changes to the tokenize module을 참조하십시오.

이 내용을 가르치는 방법

f-문자열이라는 개념은 이미 Python 커뮤니티에 널리 퍼져 있으므로, 사용자가 새로운 것을 배워야 할 근본적인 필요는 없습니다. 그러나 형식화된 문법이 새로운 가능성을 일부 허용하므로, 이 PEP가 혼란을 방지하려는 목적을 지니고 있는 만큼 어떤 구성이 가능한지 명시하면서 형식 문법을 문서에 추가하고 자세히 설명하는 것이 중요합니다.

또한 f-문자열 표현식 안에 무엇을 배치할 수 있는지 사용자가 이해할 수 있도록 간단한 틀을 제공하는 것도 유익합니다. 이 경우 저자들은 다음과 같이 요약할 수 있으므로 이 작업을 통해 언어의 이러한 측면을 훨씬 더 간단하게 설명할 수 있다고 생각합니다.

f-문자열 표현식 안에는 유효한 Python 표현식을 무엇이든 배치할 수 있습니다.

이 PEP의 변경 사항으로 인해 문자열을 둘러싸는 문자열의 따옴표와 다른 따옴표만 사용할 수 있다고 명확히 설명할 필요가 없습니다. 이제 이것이 허용되기 때문입니다. 임의의 Python 문자열에 가능한 모든 따옴표 선택이 포함될 수 있는 것처럼, 모든 f-문자열 표현식에도 그러한 따옴표를 사용할 수 있습니다. 또한 주석, 줄 바꿈 문자 또는 백슬래시와 같은 구현상의 제한 때문에 표현식 부분에서 특정 항목을 사용할 수 없다고 명확히 설명할 필요도 없습니다.

유일하게 “놀라운” 차이점은 f-문자열에서 형식을 지정할 수 있으므로 최상위 수준에서 : 문자를 허용하는 표현식은 여전히 괄호로 묶어야 한다는 점입니다. 이것은 이 작업에서 새롭게 도입된 사항은 아니지만, 이 제한이 여전히 적용된다는 점을 강조하는 것이 중요합니다. 따라서 요약을 다음과 같이 더 쉽게 수정할 수 있습니다.

f-문자열 표현식 안에는 유효한 Python 표현식을 무엇이든 배치할 수 있으며, 최상위 수준의 : 문자 뒤에 오는 모든 내용은 형식 지정으로 식별됩니다.

참조 구현

참조 구현은 implementation 포크에서 찾을 수 있습니다.

채택되지 않은 아이디어

  1. f-문자열 표현식에서 따옴표 재사용을 허용하는 것에 반대하여 제기된 가독성 관련 주장이 타당하고 매우 중요하다고 생각하지만, 저자들은 파서 수준에서 f-문자열의 따옴표 재사용을 거부하지 않는 방안을 제안하기로 결정했습니다. 그 이유는 이 PEP의 핵심 목표 중 하나가 CPython에서 f-문자열 구문 분석의 복잡성과 유지 관리 부담을 줄이는 것이기 때문이며, 이를 거부하면 그 목표에 반할 뿐 아니라 구현이 현재보다 더욱 복잡해질 수도 있기 때문입니다. 따옴표 재사용을 금지하는 일은 파서가 아니라 린터와 코드 스타일 도구에서 수행해야 하며, 이는 오늘날 언어에서 혼란을 일으키거나 읽기 어려운 다른 구성을 처리하는 방식과 같아야 한다고 생각합니다.
  2. 일부 표현식 부분에서 최상위 수준의 ':''!'를 괄호로 감싸야 한다는 제한을 해제하지 않기로 결정했습니다. 예를 들면 다음과 같습니다.:
    >>> f'Useless use of lambdas: { lambda x: x*2 }'
    SyntaxError: unexpected EOF while parsing
    

    그 이유는 실질적인 이점 없이 상당한 복잡성을 초래하기 때문입니다. 이는 : 문자가 일반적으로 f-문자열 형식 지정을 구분하기 때문입니다. 이 형식 지정은 현재 문자열로 토큰화됩니다. 토큰화기는 : 오른쪽의 내용을 문자열 또는 토큰 스트림 중 하나로 반드시 토큰화해야 하므로, 이렇게 하면 파서가 서로 다른 의미를 구분할 수 없습니다. 이를 위해서는 토큰화기가 되돌아가 다른 토큰 집합을 생성해야 하기 때문입니다. 즉, 먼저 토큰 스트림으로 시도하고 실패하면 형식 지정자를 위한 문자열로 시도해야 합니다.

    람다 및 이와 유사한 표현식을 최상위 수준에서 허용할 수 있다고 해서 근본적인 이점이 있는 것은 아니므로, 필요한 경우 이러한 표현식은 괄호로 묶어야 한다는 제한을 유지하기로 결정했습니다.:

    >>> f'Useless use of lambdas: { (lambda x: x*2) }'
    
  3. {{}} 구문과 함께 이스케이프된 중괄호(\{\})를 사용하는 것을 당분간 허용하지 않기로 결정했습니다. PEP 저자들은 이스케이프된 중괄호를 허용하는 것이 좋은 생각이라고 생각하지만, 여기서 제안하는 f-문자열의 형식화에 반드시 필요한 것은 아니며 일반적인 CPython 이슈에서 독립적으로 추가할 수 있으므로 이 PEP에는 포함하지 않기로 결정했습니다.

미해결 문제

아직 없습니다.

각주