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 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 문서는 원래 PEP 498에서 정해진 제약 중 일부를 해제하고, 파서에 직접 통합할 수 있는 f-문자열의 형식화된 문법을 제공할 것을 제안합니다. 제안하는 f-문자열의 문법적 형식화는 f-문자열을 파싱하고 해석하는 방식에 약간의 부수 효과를 가져오지만, 최종 사용자와 라이브러리 개발자에게 상당히 많은 이점을 주는 동시에 f-문자열 파싱 전용 코드의 유지보수 비용을 크게 줄여 줍니다.
동기
f-문자열이 처음 PEP 498에서 도입되었을 때, 명세는 f-문자열의 형식 문법 없이 제공되었습니다. 또한 이 명세에는 기존 렉서를 수정하지 않고 CPython에 f-문자열 파싱을 구현할 수 있도록 부과된 여러 제약이 포함되어 있습니다. 이러한 제약은 이전부터 인식되어 왔고 PEP 536에서 이를 해제하려는 시도도 있었지만, 그 작업은 하나도 구현되지 않았습니다(none of this work was ever implemented). 이러한 제약 중 일부(원래 PEP 536에서 정리한 것)는 다음과 같습니다:
- 표현식 부분 안에서 f-문자열을 구분하는 따옴표 문자를 사용할 수 없습니다:
>>> f'Magic wand: { bag['wand'] }' ^ SyntaxError: invalid syntax
- 이전에 검토되었던 우회 방법은 실행되는 코드에 이스케이프 시퀀스를 넣게 되며, 이는 f-문자열에서 금지되어 있습니다:
>>> f'Magic wand { bag[\'wand\'] } string' SyntaxError: f-string expression portion cannot include a backslash - 여러 줄 f-문자열에서도 주석이 금지되어 있습니다:
>>> f'''A complex trick: { ... bag['bag'] # recursive bags! ... }''' SyntaxError: f-string expression part cannot include '#' - 변수 이름만이 아니라 표현식을 사용하는 문자열 보간 방식을 채택한 다른 많은 언어에서는 이스케이프 시퀀스를 확장하지 않고도 표현식을 임의로 중첩할 수 있습니다. 몇 가지 예는 다음과 같습니다.
# Ruby "#{ "#{1+2}" }" # JavaScript `${`${1+2}`}` # Swift "\("\(1+2)")" # C# $"{$"{1+2}"}"
이러한 제약은 언어 사용자 관점에서 아무런 쓸모가 없으며, f-문자열 리터럴에 예외 없는 정규 문법을 부여하고 이를 전용 파싱 코드로 구현함으로써 해제할 수 있습니다.
f-문자열의 또 다른 문제는 CPython의 현재 구현이 f-문자열을 STRING 토큰으로 토큰화한 뒤 이 토큰을 후처리하는 방식에 의존한다는 것입니다. 이 방식에는 다음과 같은 문제가 있습니다:
- CPython 파서의 유지 보수 비용이 상당히 늘어납니다. 파싱 코드를 손으로 작성해야 하기 때문이며, 이로 인해 지금까지 상당히 많은 불일치와 버그가 생겼습니다. C로 파싱 코드를 손으로 작성하고 유지 보수하는 일은 원래 렉서 버퍼에 대한 수동 메모리 관리를 많이 다뤄야 하므로, 늘 오류가 생기기 쉽고 위험한 일로 여겨져 왔습니다.
- f-문자열 파싱 코드는 PEP 617에서 처음 도입된 새 PEG 파서가 가능하게 한 개선된 새 오류 메시지 메커니즘을 사용할 수 없습니다. 이러한 오류 메시지가 가져온 개선은 크게 환영받았지만, 안타깝게도 f-문자열은 파싱 기구의 별도 부분에서 파싱되기 때문에 그 혜택을 받을 수 없습니다. 이는 특히 안타까운 일인데, 표현식 부분 안에서 일어나는 암묵적인 토큰화 방식이 달라서 혼란을 줄 수 있는 f-문자열의 문법적 특징이 여럿 있기 때문입니다(예를 들어
f"{y:=3}"는 대입 표현식이 아닙니다). - 다른 Python 구현은 f-문자열을 올바르게 구현했는지 알 방법이 없습니다. 다른 언어 기능과 달리 f-문자열은 공식 Python 문법에 포함되어 있지 않기 때문입니다. 이것이 중요한 이유는 여러 주요 대체 구현(such as PyPy)이 CPython의 PEG 파서를 사용하거나 공식 PEG 문법을 바탕으로 자체 문법을 만들고 있기 때문입니다. f-문자열이 별도의 파서를 사용한다는 사실 때문에 이러한 대안 구현은 공식 문법을 활용하지 못하고, 문법에서 비롯되는 오류 메시지 개선의 혜택도 받지 못합니다.
이 제안의 한 버전은 원래 Python-Dev에서 논의되었고(discussed on Python-Dev) 파이썬 언어 서밋 2022에서 발표되었으며(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 언어 전반)의 정신적 부담과 인지적 복잡성을 줄여 줍니다.
- 표현식 구성 요소는 일반 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 절을 참조하십시오.
- 대부분의 사람이 직관적이지 않다고 느껴 온 또 다른 문제는 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'
- 이 문서에서 제안하는 변경 이전에는 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-문자열 디버깅 표현식’을 지원하기 위해 “역토큰화(untokenizing)”를 허용해야 하는데, 임의의 중첩이 허용되면 이 작업이 특히 부담스러워집니다). 따라서 구현은 필요하다면 중첩 깊이에 자유롭게 제한을 둘 수 있습니다. 이는 드문 상황이 아니라는 점에 유의하십시오. 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-문자열 디버그 표현식은 {와 = 문자 뒤의 공백을 포함하여 표현식 안의 공백을 보존하므로, 단순한 “역토큰화”만으로는 충분하지 않습니다. 즉, 관련 토큰만 유지할 것이 아니라 f-문자열 표현식 부분의 원시 문자열 내용을 그대로 보존해야 합니다.
파서/렉서 구현이 이 문제를 어떻게 처리할지는 물론 각 구현에 달려 있습니다.
새로운 토큰
세 개의 새로운 토큰이 도입됩니다: FSTRING_START, FSTRING_MIDDLE, FSTRING_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과 유사한 토크나이저가 이 새로운 토큰들을 내보내도록 수정하는 알고리즘의 초안은 다음과 같습니다:
- 렉서가 (‘f/F’ 문자와 가능한 따옴표 중 하나를 감지하여) f-문자열이 시작됨을 감지하면, 유효한 따옴표(
",""",','''중 하나)가 감지될 때까지 계속 진행한 뒤, 캡처한 내용(‘f/F’와 시작 따옴표)을 담은FSTRING_START토큰을 내보냅니다. “F-문자열 토큰화”를 위한 새 토크나이저 모드를 토크나이저 모드 스택에 푸시합니다. 2단계로 이동합니다. - 다음 중 하나를 만날 때까지 토큰을 계속 소비합니다:
- 여는 따옴표와 같은 닫는 따옴표.
- “형식 지정자 모드”(3단계 참조)인 경우, 여는 중괄호(
{), 닫는 중괄호(}) 또는 줄바꿈 토큰(\n). - “형식 지정자 모드”(3단계 참조)가 아닌 경우, 바로 뒤에 또 다른 여는/닫는 중괄호가 오지 않는 여는 중괄호(
{) 또는 닫는 중괄호(}).
모든 경우에, 문자 버퍼가 비어 있지 않으면 지금까지 캡처한 내용을 담은
FSTRING_MIDDLE토큰을 방출하되, 이중 여는/닫는 중괄호는 모두 단일 여는/닫는 중괄호로 변환합니다. 이제 만난 문자에 따라 다음과 같이 진행합니다:- 여는 따옴표와 일치하는 닫는 따옴표를 만나면 4단계로 이동합니다.
- (바로 뒤에 또 다른 여는 괄호가 오지 않는) 여는 괄호를 만나면 3단계로 이동합니다.
- (바로 뒤에 또 다른 닫는 괄호가 오지 않는) 닫는 괄호를 만나면 닫는 괄호에 대한 토큰을 방출하고 2단계로 이동합니다.
- “f-문자열 내 일반 Python 토큰화”를 위한 새 토크나이저 모드를 토크나이저 모드 스택에 푸시하고 이 모드로 토큰화를 진행합니다. 이 모드는 f-문자열 부분에 들어갈 때 푸시된 여는 괄호 토큰과 같은 중첩 수준에서
:또는}문자를 만날 때까지 “일반 Python 토큰화”처럼 토큰화합니다. 이 모드를 사용하여 중단 지점 중 하나에 도달할 때까지 토큰을 방출합니다. 중단 지점에 도달하면 만난 중단 문자에 해당하는 토큰을 방출하고, 현재 토크나이저 모드를 토크나이저 모드 스택에서 팝한 다음 2단계로 이동합니다. 중단 지점이:문자이면 “형식 지정자” 모드로 2단계에 진입합니다. - 캡처한 내용으로
FSTRING_END토큰을 내보내고, 현재 토크나이저 모드(“f-문자열 토큰화”에 해당)를 팝한 다음 “일반 Python 모드”로 돌아갑니다.
물론 앞서 언급했듯이, 임의의 토크나이저에 대해 이를 어떻게 수행해야 하는지 정확한 명세를 제공하는 것은 불가능합니다. 이는 변경할 렉서의 구체적인 구현과 특성에 따라 달라지기 때문입니다.
새 문법의 결과
아래에서 설명하듯이, PEP에서 언급한 모든 제약이 f-문자열 리터럴에서 해제됩니다:
- 이제 표현식 부분에 f-문자열 리터럴을 구분하는 데 사용된 것과 같은 종류의 따옴표로 구분된 문자열이 포함될 수 있습니다.
- 이제 Python 코드의 다른 곳과 마찬가지로 표현식 안에도 백슬래시가 나타날 수 있습니다. f-문자열 리터럴 안에 중첩된 문자열의 경우, 가장 안쪽의 문자열이 평가될 때 이스케이프 시퀀스가 확장됩니다.
- 이제 표현식 괄호 안에서 줄 바꿈이 허용됩니다. 이는 이제 다음과 같은 코드가 허용된다는 뜻입니다:
>>> x = 1 >>> f"___{ ... x ... }___" '___1___' >>> f"___{( ... x ... )}___" '___1___'
- f-문자열의 표현식 부분 안에서
#문자를 사용한 주석이 허용됩니다. 주석을 사용할 때는 표현식 부분의 닫는 괄호(})가 주석이 있는 줄과 다른 줄에 있어야 한다는 점에 유의하십시오. 그렇지 않으면 닫는 괄호가 주석의 일부로 취급되어 무시됩니다.
따옴표 재사용에 관한 고려 사항
여기에서 제안하는 문법의 결과 중 하나는, 앞서 언급했듯이 이제 f-문자열 표현식에 바깥쪽 f-문자열 리터럴을 구분하는 데 사용된 것과 같은 종류의 따옴표로 구분된 문자열이 포함될 수 있다는 점입니다. 예를 들면 다음과 같습니다:
>>> f" something { my_dict["key"] } something else "
이 PEP에 대한 토론 스레드에서 이 측면에 관해 여러 우려가 제기되었습니다. 이 PEP의 채택 여부를 결정할 때 이러한 우려를 고려해야 하므로 여기에 정리하고자 합니다.
이러한 반대 의견 중 일부는 다음과 같습니다:
- 많은 사람들은 같은 문자열 안에서 따옴표를 재사용하는 것이 혼란스럽고 읽기 어렵다고 생각합니다. 따옴표 재사용을 허용하면 오늘날 Python이 가진 속성을 위반하게 되기 때문입니다. 그 속성이란 문자열이 같은 종류의 따옴표로 이루어진 연속된 한 쌍에 의해 완전히 구분된다는 점이며, 이는 그 자체로 매우 단순한 규칙입니다. 따옴표 재사용이 사람이 파싱하기에 더 어려워 코드의 가독성을 떨어뜨릴 수 있는 이유 중 하나는 (다른 구분자와 달리) 시작과 끝의 따옴표 문자가 같다는 점입니다.
- 일부 사용자는 따옴표 재사용이 정규 표현식이나 단순한 구분자 매칭 도구처럼 문자열과 f-문자열을 감지하는 단순한 메커니즘에 의존하는 일부 렉서 및 문법 강조 도구를 망가뜨릴 수 있다는 우려를 제기했습니다. f-문자열에 따옴표 재사용을 도입하면 이러한 도구가 계속 동작하도록 유지하기가 더 까다로워지거나 도구가 아예 동작하지 않게 될 것입니다(예를 들어 정규 표현식은 구분자로 이루어진 임의의 중첩 구조를 파싱할 수 없습니다). 표준 라이브러리에 포함된 IDLE 편집기는 f-문자열에 문법 강조를 올바르게 적용하려면 약간의 작업이 필요할 수 있는 도구의 한 예입니다.
다음은 찬성하는 인자 중 일부입니다:
- 비슷한 구문 구조(보통 “문자열 보간”이라고 부릅니다)를 허용하는 많은 언어는 따옴표 재사용과 임의의 중첩을 허용합니다. 이러한 언어에는 JavaScript, Ruby, C#, Bash, Swift를 비롯한 많은 언어가 있습니다. 많은 언어가 따옴표 재사용을 허용한다는 사실은 Python에서도 이를 허용해야 한다는 설득력 있는 인자가 될 수 있습니다. 다른 언어에서 넘어온 사용자에게 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-문자열 안의 표현식이 사용되는 문맥에 적절히 맞도록 보장하기 위해 복잡한 알고리즘에 의존합니다. 이러한 복잡한 알고리즘에는 (바깥쪽 따옴표를 추적하여) 사용되지 않은 따옴표 종류를 찾아내는 일이나, 가능하면 백슬래시를 포함하지 않는 문자열 표현을 생성하는 일 같은 과제가 따릅니다. 따옴표 재사용과 백슬래시를 허용하면 일반적인 Python 표현식 로직을 f-문자열 안팎에서 특별한 처리 없이 사용할 수 있으므로, f-문자열을 다루는 코드 생성기가 상당히 단순해질 것입니다.
- 따옴표 재사용을 제한하면 제안된 변경 사항의 구현 복잡도가 상당히 증가할 것입니다. 파서가 따옴표를 재사용하는 표현식을 거부해야 하는지 알려면, 특정 따옴표로 된 f-문자열의 표현식 부분을 파싱하고 있다는 문맥을 파서가 가지고 있어야 하기 때문입니다. (PEG 파서처럼) 임의로 백트래킹할 수 있는 파서에서 이러한 문맥을 계속 유지하는 것은 간단한 일이 아닙니다. f-문자열이 임의로 중첩될 수 있어 여러 종류의 따옴표를 거부해야 할 수도 있다는 점을 고려하면 문제는 더욱 복잡해집니다.
커뮤니티의 피드백을 모으기 위해, PEP의 이러한 측면에 대해 커뮤니티가 어떻게 생각하는지 파악하고자 설문 조사가 시작되었습니다.
하위 호환성
이 PEP는 Python 언어에 하위 호환성을 깨뜨리는 구문적 또는 의미적 변경을 도입하지 않습니다. 다만 (도구 작성자가 f-문자열을 올바르게 토큰화할 수 있도록) 새로운 f-문자열 토큰을 지원하려면 tokenize 모듈(표준 라이브러리의 준공개 부분)을 갱신해야 합니다. tokenize의 공개 API가 어떤 영향을 받는지에 대한 자세한 내용은 Changes to the tokenize module을 참조하십시오.
교육 방법
f-문자열이라는 개념은 이미 Python 커뮤니티에 널리 퍼져 있으므로, 사용자가 근본적으로 새로 배워야 할 것은 없습니다. 그러나 형식화된 문법으로 몇 가지 새로운 가능성이 생기므로, 형식 문법을 문서에 추가하고 자세히 설명하는 것이 중요합니다. 이 PEP는 혼란을 피하는 것을 목표로 하므로, 어떤 구문이 가능한지 명시적으로 언급해야 합니다.
또한 f-문자열 표현식 안에 무엇을 넣을 수 있는지 이해할 수 있는 간단한 프레임워크를 사용자에게 제공하는 것도 유익합니다. 이 경우 저자들은 이 작업 덕분에 언어의 이 측면을 설명하기가 한층 더 쉬워질 것이라고 생각합니다. 다음과 같이 요약할 수 있기 때문입니다:
f-문자열 표현식 안에는 유효한 Python 표현식이라면 무엇이든 넣을 수 있습니다.
이 PEP의 변경 사항이 적용되면 문자열 따옴표가 바깥 문자열의 따옴표와 달라야 한다고 설명할 필요가 없습니다. 이제는 같은 따옴표도 허용되기 때문입니다. 임의의 Python 문자열에 어떤 따옴표든 쓸 수 있듯이, 모든 f-문자열 표현식에도 어떤 따옴표든 쓸 수 있습니다. 또한 주석, 개행 문자, 백슬래시처럼 구현상의 제약 때문에 표현식 부분에 특정 요소를 쓸 수 없다고 설명할 필요도 없습니다.
유일하게 “놀라운” 차이점은 f-문자열에서 서식을 지정할 수 있기 때문에, 최상위 수준에서 : 문자를 허용하는 표현식은 여전히 괄호로 감싸야 한다는 점입니다. 이는 이 작업에서 새로 생긴 것은 아니지만, 이 제약이 여전히 유지된다는 점을 강조하는 것이 중요합니다. 이에 따라 요약을 다음과 같이 쉽게 고칠 수 있습니다:
f-문자열 표현식 안에는 유효한 Python 표현식이라면 무엇이든 넣을 수 있으며, 최상위 수준의:문자 뒤에 오는 모든 것은 서식 명세로 인식됩니다.
참조 구현
참조 구현은 implementation 포크에서 찾을 수 있습니다.
기각된 아이디어
- f-문자열 표현식에서 따옴표 재사용을 허용하는 것에 반대하여 제기된 가독성 관련 인자가 타당하고 매우 중요하다고 생각하지만, 저희는 파서 수준에서 f-문자열의 따옴표 재사용을 거부하지 않는 방안을 제안하기로 결정했습니다. 그 이유는 이 PEP의 핵심 목표 중 하나가 CPython에서 f-문자열 파싱의 복잡성과 유지 보수 부담을 줄이는 것인데, 따옴표 재사용을 거부하면 그 목표에 어긋날 뿐만 아니라 구현이 현재보다 훨씬 더 복잡해질 수도 있기 때문입니다. 우리는 따옴표 재사용 금지는 파서가 아니라 린터와 코드 스타일 도구에서 처리해야 한다고 믿습니다. 오늘날 언어의 다른 혼란스럽거나 읽기 어려운 구문을 다루는 것과 같은 방식입니다.
- 일부 표현식 부분에서 최상위 수준의
':'및'!'를 괄호로 감싸야 한다는 제약은 해제하지 않기로 하였습니다. 예::>>> f'Useless use of lambdas: { lambda x: x*2 }' SyntaxError: unexpected EOF while parsing
그 이유는 이렇게 하면 실질적인 이점 없이 상당한 복잡성이 생기기 때문입니다. 이는
:문자가 일반적으로 f-문자열의 서식 명세를 구분하기 때문입니다. 이 서식 명세는 현재 문자열로 토큰화됩니다. 토크나이저는:오른쪽에 있는 것을 문자열 또는 토큰 스트림 중 하나로 토큰화해야 하므로(MUST), 파서가 서로 다른 의미를 구별할 수 없습니다. 그렇게 하려면 토크나이저가 되돌아가서(backtrack) 다른 토큰 집합을 생성해야 하기 때문입니다(즉, 먼저 토큰 스트림으로 시도하고, 실패하면 형식 지정자를 위한 문자열로 시도해야 합니다).최상위 수준에서 람다 및 이와 유사한 표현식을 허용할 수 있다고 해서 근본적인 이점이 있는 것은 아니므로, 필요한 경우 이러한 표현식을 괄호로 감싸야 한다는 제약을 유지하기로 하였습니다.:
>>> f'Useless use of lambdas: { (lambda x: x*2) }'
{{및}}문법 외에 이스케이프된 중괄호(\{및\})를 사용하는 것은 (당분간) 허용하지 않기로 하였습니다. PEP 저자들은 이스케이프된 중괄호를 허용하는 것이 좋은 아이디어라고 생각하지만, 여기에서 제안하는 f-문자열 형식화에 꼭 필요한 것은 아니며 일반적인 CPython 이슈를 통해 별도로 추가할 수 있으므로 이 PEP에는 포함하지 않기로 하였습니다.
미해결 문제
아직 없습니다.
각주
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.