PEP 498 – 리터럴 문자열 보간
- Author:
- Eric V. Smith <eric at trueblade.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 01-Aug-2015
- Python-Version:
- 3.6
- Post-History:
- 07-Aug-2015, 30-Aug-2015, 04-Sep-2015, 19-Sep-2015, 06-Nov-2016
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python은 텍스트 문자열의 형식을 지정하는 여러 방법을 지원합니다. 여기에는 %-포매팅 [1], str.format() [2], 그리고 string.Template [3] 등이 있습니다. 이 방법들은 각각 장점이 있지만, 실제로 사용하기 번거롭게 만드는 단점도 있습니다. 이 PEP는 새로운 문자열 포매팅 메커니즘인 리터럴 문자열 보간을 추가할 것을 제안했습니다. 이 PEP에서는 이러한 문자열을 “f-문자열(f-strings)”이라고 부릅니다. 이 이름은 이러한 문자열을 나타내는 데 쓰이는 앞 글자에서 따온 것으로, “포매팅된 문자열(formatted strings)”을 뜻합니다.
이 PEP는 기존의 문자열 포매팅 메커니즘 중 어느 것도 제거하거나 폐기할 것을 제안하지 않습니다.
f-문자열은 최소한의 문법으로 문자열 리터럴 안에 표현식을 삽입하는 방법을 제공합니다. f-문자열은 상수 값이 아니라 실제로는 런타임에 평가되는 표현식이라는 점에 유의해야 합니다. Python 소스 코드에서 f-문자열은 ‘f’ 접두사가 붙은 리터럴 문자열이며, 중괄호 안에 표현식을 포함합니다. 표현식은 그 값으로 치환됩니다. 몇 가지 예는 다음과 같습니다:
>>> import datetime
>>> name = 'Fred'
>>> age = 50
>>> anniversary = datetime.date(1991, 10, 12)
>>> f'My name is {name}, my age next year is {age+1}, my anniversary is {anniversary:%A, %B %d, %Y}.'
'My name is Fred, my age next year is 51, my anniversary is Saturday, October 12, 1991.'
>>> f'He said his name is {name!r}.'
"He said his name is 'Fred'."
비슷한 기능이 PEP 215에서 제안된 적이 있습니다. PEP 215는 Python 표현식의 일부만 지원하자고 제안했으며, PEP 3101에서 도입된 타입별 문자열 형식 지정(__format__() 메서드)은 지원하지 않았습니다.
근거
이 PEP는 Python에서 형식 문자열을 만드는 더 간단한 방법이 필요하다는 바람에서 출발했습니다. 기존의 포매팅 방법은 오류가 발생하기 쉽거나, 유연하지 않거나, 번거롭습니다.
%-포매팅은 지원하는 타입이 제한되어 있습니다. int, str, double만 포매팅할 수 있습니다. 그 밖의 모든 타입은 지원되지 않거나, 포매팅 전에 이 타입 중 하나로 변환됩니다. 또한 단일 값을 전달할 때 빠지기 쉬운 잘 알려진 함정이 있습니다:
>>> msg = 'disk failure'
>>> 'error: %s' % msg
'error: disk failure'
그러나 msg가 튜플이라면 같은 코드는 실패합니다:
>>> msg = ('disk failure', 32)
>>> 'error: %s' % msg
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting
방어적으로 작성하려면 다음 코드를 사용해야 합니다:
>>> 'error: %s' % (msg,)
"error: ('disk failure', 32)"
str.format()은 %-포매팅의 이러한 문제 중 일부를 해결하기 위해 추가되었습니다. 특히 이 방법은 일반적인 함수 호출 문법을 사용하며(따라서 여러 매개변수를 지원하며), 문자열로 변환되는 객체의 __format__() 메서드를 통해 확장할 수 있습니다. 자세한 근거는 PEP 3101을 참조하십시오. 이 PEP는 기존 Python 문자열 형식 지정 메커니즘과의 연속성을 제공하기 위해 str.format() 문법과 그 내부 기법의 상당 부분을 재사용합니다.
그러나 str.format()에도 문제가 없는 것은 아닙니다. 그중 가장 큰 문제는 장황하다는 점입니다. 예를 들어, 여기서는 value라는 텍스트가 반복됩니다:
>>> value = 4 * 20
>>> 'The value is {value}.'.format(value=value)
'The value is 80.'
가장 단순한 형태에서도 약간의 상용구 코드가 필요하며, 자리 표시자에 삽입되는 값이 자리 표시자가 위치한 곳에서 멀리 떨어져 있는 경우도 있습니다:
>>> 'The value is {}.'.format(value)
'The value is 80.'
f-문자열을 사용하면 다음과 같이 됩니다:
>>> f'The value is {value}.'
'The value is 80.'
f-문자열은 Python 표현식의 값을 문자열 안에 포함하는 간결하고 읽기 쉬운 방법을 제공합니다.
이러한 의미에서 string.Template과 %-포매팅은 str.format()과 비슷한 단점을 가지고 있으며, 지원하는 포매팅 옵션도 더 적습니다. 특히 이들은 __format__ 프로토콜을 지원하지 않으므로 특정 객체가 문자열로 변환되는 방식을 제어할 방법이 없으며, 문자열로 변환되는 방식을 직접 제어하려는 다른 타입(Decimal과 datetime 등)으로 확장할 수도 없습니다. 이 예제는 string.Template으로는 불가능합니다:
>>> value = 1234
>>> f'input={value:#06x}'
'input=0x04d2'
그리고 %-포매팅과 string.Template 모두 다음과 같은 포매팅을 제어할 수 없습니다:
>>> date = datetime.date(1991, 10, 12)
>>> f'{date} was on a {date:%A}'
'1991-10-12 was on a Saturday'
globals()나 locals()를 사용하지 않음
python-dev에서의 논의 [4] 에서는 locals()와 globals() 또는 그에 상응하는 것을 사용하는 여러 해결책이 제시되었습니다. 이들 모두에는 여러 가지 문제가 있습니다. 그중 하나는 클로저에서 달리 사용되지 않는 변수를 참조하는 문제입니다. 다음을 생각해 보십시오:
>>> def outer(x):
... def inner():
... return 'x={x}'.format_map(locals())
... return inner
...
>>> outer(42)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in inner
KeyError: 'x'
이 코드는 컴파일러가 클로저 안에 x에 대한 참조를 추가하지 않았기 때문에 오류를 반환합니다. 이것이 동작하게 하려면 x에 대한 참조를 수동으로 추가해야 합니다:
>>> def outer(x):
... def inner():
... x
... return 'x={x}'.format_map(locals())
... return inner
...
>>> outer(42)()
'x=42'
또한 locals()나 globals()를 사용하면 정보가 누출됩니다. 호출자의 locals()나 globals()에 접근할 수 있는 피호출 루틴은 문자열 보간을 수행하는 데 필요한 것보다 훨씬 많은 정보에 접근할 수 있습니다.
Guido는 문자열 보간을 개선하는 해결책이라면 어떤 것이든 그 구현에서 locals()나 globals()를 사용하지 않을 것이라고 밝혔습니다 [5]. (이는 사용자가 locals()나 globals()를 넘기는 것을 금지하지는 않습니다. 단지 이를 요구하지 않으며, 내부적으로 이 함수들을 사용하는 것도 허용하지 않는다는 뜻입니다.)
명세
소스 코드에서 f-문자열은 문자 ‘f’ 또는 ‘F’가 접두사로 붙은 문자열 리터럴입니다. 이 PEP에서 ‘f’를 사용하는 모든 곳에서 ‘F’도 사용할 수 있습니다. ‘f’는 ‘r’ 또는 ‘R’과 순서에 상관없이 결합하여 raw f-문자열 리터럴을 만들 수 있습니다. ‘f’는 ‘b’와 결합할 수 없습니다: 이 PEP는 바이너리 f-문자열을 추가하는 것을 제안하지 않습니다. ‘f’는 ‘u’와 결합할 수 없습니다.
소스 파일을 토큰화할 때 f-문자열은 일반 문자열, raw 문자열, 바이너리 문자열, 삼중 따옴표 문자열과 같은 규칙을 사용합니다. 즉, 문자열은 시작한 문자와 같은 문자로 끝나야 합니다. 작은따옴표로 시작하면 작은따옴표로 끝나야 하는 식입니다. 이는 현재 Python 코드를 훑으며 문자열을 찾는 코드라면 어떤 것이든 간단히 수정하여 f-문자열을 인식하게 만들 수 있다는 뜻입니다(물론 f-문자열 내부를 파싱하는 것은 별개의 문제입니다).
토큰화된 후 f-문자열은 리터럴 문자열과 표현식으로 파싱됩니다. 표현식은 중괄호 '{'와 '}' 안에 나타납니다. 표현식을 찾기 위해 문자열을 스캔하는 동안, f-문자열의 리터럴 부분에 있는 이중 중괄호 '{{' 또는 '}}'는 대응하는 단일 중괄호로 치환됩니다. 이중으로 된 리터럴 여는 중괄호는 표현식의 시작을 나타내지 않습니다. 문자열의 리터럴 부분에 있는 단일 닫는 중괄호 '}'는 오류입니다: 단일 닫는 중괄호를 나타내려면 리터럴 닫는 중괄호를 '}}'처럼 두 번 써야 합니다.
f-문자열에서 중괄호 바깥 부분은 리터럴 문자열입니다. 그런 다음 이 리터럴 부분들이 디코딩됩니다. raw가 아닌 f-문자열의 경우, 여기에는 '\n', '\"', "\'", '\xhh', '\uxxxx', '\Uxxxxxxxx' 같은 역슬래시 이스케이프와 이름 있는 유니코드 문자 '\N{name}'을 해당 유니코드 문자로 변환하는 작업이 포함됩니다 [6].
역슬래시는 표현식 안의 어디에도 나타날 수 없습니다. '#' 문자를 사용하는 주석은 표현식 안에서 허용되지 않습니다.
각 표현식 뒤에는 선택적으로 타입 변환을 지정할 수 있습니다. 허용되는 변환은 '!s', '!r', '!a'입니다. 이들은 str.format()에서와 똑같이 처리됩니다: '!s'는 표현식에 대해 str()을 호출하고, '!r'는 표현식에 대해 repr()을 호출하며, '!a'는 표현식에 대해 ascii()를 호출합니다. 이러한 변환은 format() 호출 전에 적용됩니다. '!s'를 사용할 유일한 이유는 표현식의 타입이 아니라 str에 적용되는 형식 지정자를 지정하려는 경우입니다.
f-문자열은 str.format과 같은 형식 지정자 미니 언어를 사용합니다. str.format()과 마찬가지로, f-문자열 안에 선택적인 형식 지정자를 포함할 수 있으며, 형식 지정자는 표현식(또는 타입 변환이 지정된 경우 타입 변환)과 콜론으로 구분됩니다. 형식 지정자를 제공하지 않으면 빈 문자열이 사용됩니다.
따라서 f-문자열은 다음과 같은 모습입니다:
f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '
그런 다음 표현식은 형식 지정자를 인자로 하여 __format__ 프로토콜로 형식이 지정됩니다. 그 결과 값은 f-문자열의 값을 만들 때 사용됩니다.
__format__()이 각 값에 대해 직접 호출되지는 않는다는 점에 유의하십시오. 실제 코드는 type(value).__format__(value, format_spec) 또는 format(value, format_spec)과 동등한 방식을 사용합니다. 자세한 내용은 내장 format() 함수의 설명서를 참조하십시오.
표현식은 문자열이나 소괄호, 대괄호, 중괄호 바깥에서 ':'나 '!'를 포함할 수 없습니다. 단, 예외적으로 '!=' 연산자는 특수한 경우로 허용됩니다.
이스케이프 시퀀스
백슬래시는 f-문자열의 표현식 부분 안에 나타날 수 없으므로, 예를 들어 f-문자열 안에서 따옴표를 이스케이프하는 데 백슬래시를 사용할 수 없습니다:
>>> f'{\'quoted string\'}'
File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash
표현식 안에서는 다른 종류의 따옴표를 사용할 수 있습니다:
>>> f'{"quoted string"}'
'quoted string'
백슬래시 이스케이프는 f-문자열의 문자열 부분 안에는 나타날 수 있습니다.
결과 문자열 값에 리터럴 중괄호가 나타나게 하는 올바른 방법은 중괄호를 두 번 쓰는 것이라는 점에 유의하십시오:
>>> f'{{ {4*10} }}'
'{ 40 }'
>>> f'{{{4*10}}}'
'{40}'
Python의 모든 raw 문자열과 마찬가지로, raw f-문자열에서는 이스케이프 처리가 이루어지지 않습니다:
>>> fr'x={4*10}\n'
'x=40\\n'
Python의 문자열 토큰화 규칙 때문에 f-문자열 f'abc {a['x']} def'는 유효하지 않습니다. 토크나이저는 이를 f'abc {a[', x, ']} def'의 3개 토큰으로 파싱합니다. 일반 문자열과 마찬가지로, 이 문제는 원시 문자열을 사용해도 해결할 수 없습니다. 이 f-문자열을 올바르게 작성하는 방법은 여러 가지가 있습니다: 다른 따옴표 문자를 사용하는 방법:
f"abc {a['x']} def"
또는 삼중 따옴표를 사용하는 방법:
f'''abc {a['x']} def'''
코드 동등성
f-문자열을 구현하는 데 사용되는 정확한 코드는 규정되어 있지 않습니다. 그러나 문자열로 변환되는 모든 내장된 값은 해당 값의 __format__ 메서드를 사용한다는 것이 보장됩니다. 이것은 str.format()이 값을 문자열로 변환할 때 사용하는 것과 같은 메커니즘입니다.
예를 들어, 다음 코드는:
f'abc{expr1:spec1}{expr2!r:spec2}def{expr3}ghi'
다음과 같이 평가될 수 있습니다:
'abc' + format(expr1, spec1) + format(repr(expr2), spec2) + 'def' + format(expr3) + 'ghi'
표현식 평가
문자열에서 추출된 표현식은 f-문자열이 나타난 컨텍스트에서 평가됩니다. 즉, 표현식은 지역 변수와 전역 변수에 완전히 접근할 수 있습니다. 함수 호출과 메서드 호출을 포함하여 유효한 Python 표현식이라면 무엇이든 사용할 수 있습니다.
f-문자열은 소스 코드에서 문자열이 나타나는 위치에서 평가되므로, f-문자열을 사용한다고 해서 표현력이 더해지지는 않습니다. 보안상 추가로 우려할 점도 없습니다. f-문자열 안에 넣지 않고 같은 표현식을 그냥 작성할 수도 있었기 때문입니다:
>>> def foo():
... return 20
...
>>> f'result={foo()}'
'result=20'
다음과 동등합니다:
>>> 'result=' + str(foo())
'result=20'
표현식은 ast.parse('(' + expression + ')', '<fstring>', 'eval')와 동등한 방식으로 파싱됩니다 [7].
표현식은 평가 전에 암묵적인 괄호로 감싸지므로 줄바꿈을 포함할 수 있다는 점에 유의하십시오. 예를 들어:
>>> x = 0
>>> f'''{x
... +1}'''
'1'
>>> d = {0: 'zero'}
>>> f'''{d[0
... ]}'''
'zero'
형식 지정자
형식 지정자에도 평가되는 표현식을 포함할 수 있습니다. 이를 통해 다음과 같은 코드를 작성할 수 있습니다:
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal('12.34567')
>>> f'result: {value:{width}.{precision}}'
'result: 12.35'
형식 지정자 안의 표현식이 (필요한 경우) 평가되고 나면, 형식 지정자는 f-문자열 평가기가 해석하지 않습니다. str.format()에서와 마찬가지로, 포맷 지정자는 포맷되는 객체의 __format__() 메서드에 그대로 전달될 뿐입니다.
문자열 이어 붙이기
인접한 f-문자열과 일반 문자열은 이어 붙여집니다. 일반 문자열은 컴파일 시간에 이어 붙여지고, f-문자열은 실행 시간에 이어 붙여집니다. 예를 들어, 다음 표현식은:
>>> x = 10
>>> y = 'hi'
>>> 'a' 'b' f'{x}' '{c}' f'str<{y:^4}>' 'd' 'e'
다음 값을 산출합니다:
'ab10{c}str< hi >de'
이 실행 시간 이어 붙이기의 정확한 방법은 명시되어 있지 않지만, 위 코드는 다음과 같이 평가될 수 있습니다:
'ab' + format(x) + '{c}' + 'str<' + format(y, '^4') + '>de'
각 f-문자열은 인접한 f-문자열과 이어 붙여지기 전에 완전히 평가됩니다. 즉, 다음 코드는:
>>> f'{x' f'}'
첫 번째 f-문자열에 닫는 중괄호가 없으므로 문법 오류입니다.
오류 처리
f-문자열을 처리할 때 컴파일 시간 오류나 실행 시간 오류가 발생할 수 있습니다. 컴파일 시간 오류는 f-문자열을 스캔할 때 감지할 수 있는 오류로 한정됩니다. 이러한 오류는 모두 SyntaxError를 발생시킵니다.
짝이 맞지 않는 중괄호:
>>> f'x={x'
File "<stdin>", line 1
SyntaxError: f-string: expecting '}'
유효하지 않은 표현식:
>>> f'x={!x}'
File "<stdin>", line 1
SyntaxError: f-string: empty expression not allowed
런타임 오류는 f-문자열 안의 표현식을 평가할 때 발생합니다. f-문자열은 여러 번 평가될 수 있으며, 어떤 때는 정상적으로 동작하고 다른 때는 오류를 일으킬 수 있다는 점에 유의하십시오:
>>> d = {0:10, 1:20}
>>> for i in range(3):
... print(f'{i}:{d[i]}')
...
0:10
1:20
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyError: 2
또는:
>>> for x in (32, 100, 'fifty'):
... print(f'x = {x:+3}')
...
'x = +32'
'x = +100'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: Sign not allowed in string format specifier
표현식의 앞뒤 공백은 무시됩니다
가독성을 위해 표현식의 앞뒤 공백은 무시됩니다. 이는 평가 전에 표현식을 괄호로 감싸는 데서 생기는 부산물입니다.
표현식의 평가 순서
f-문자열 안의 표현식은 왼쪽에서 오른쪽 순서로 평가됩니다. 이는 표현식에 부수 효과가 있는 경우에만 알아챌 수 있습니다:
>>> def fn(l, incr):
... result = l[0]
... l[0] += incr
... return result
...
>>> lst = [0]
>>> f'{fn(lst,2)} {fn(lst,3)}'
'0 2'
>>> f'{fn(lst,2)} {fn(lst,3)}'
'5 7'
>>> lst
[10]
논의
python-ideas 논의
python-ideas [8]에서 이루어진 논의의 대부분은 다음 세 가지 쟁점에 집중되었습니다:
- f-문자열을 표기하는 방법,
- f-문자열에서 표현식의 위치를 지정하는 방법, 그리고
- 완전한 Python 표현식을 허용할지 여부.
f-문자열을 표기하는 방법
보간 문자열에 포함된 표현식을 평가하는 데 컴파일러가 관여해야 하므로, 어떤 문자열을 평가해야 하는지 컴파일러에 알려 줄 방법이 있어야 합니다. 이 PEP는 문자열 리터럴 앞에 붙는 'f' 문자를 선택했습니다. 이는 'b'와 'r' 접두사가 컴파일 시점에 문자열 자체의 의미를 바꾸는 방식과 비슷합니다. 'i'와 같은 다른 접두사도 제안되었습니다. 어느 선택지도 다른 것보다 나아 보이지 않았으므로 'f'가 선택되었습니다.
또 다른 선택지는 Format()과 같이 컴파일러가 알고 있는 특수 함수를 지원하는 것이었습니다. 이는 Python에는 지나친 마법처럼 보입니다. 기존 식별자와 충돌할 가능성이 있을 뿐만 아니라, PEP 작성자는 문자열 접두사 문자로 마법을 드러내는 편이 더 낫다고 생각합니다.
f-문자열에서 표현식의 위치를 지정하는 방법
이 PEP는 문자열 안에서 치환될 텍스트를 구분하기 위해 str.format()과 같은 문법을 지원합니다. 즉, 표현식은 중괄호 안에 들어갑니다. string.Template의 $identifier나 ${expression}과 같은 다른 선택지도 제안되었습니다.
$identifier는 셸 스크립트 작성자나 다른 몇몇 언어 사용자에게 분명 더 친숙하겠지만, Python에서는 str.format()이 매우 많이 사용됩니다. Python 표준 라이브러리를 간단히 검색해 보면 string.Template의 사용처는 몇 군데에 불과하지만, str.format()의 사용처는 수백 군데에 이릅니다.
제안된 또 다른 대안은 치환 텍스트를 \{와 } 사이, 또는 \{와 \} 사이에 두는 것이었습니다. 모든 문자열 리터럴이 보간을 지원한다면 이 문법이 바람직할 수도 있겠지만, 이 PEP는 앞에 'f'가 붙어 이미 표시된 문자열만 지원합니다. 따라서 이 PEP는 최종 사용자가 str.format()에 익숙하다는 점을 활용하기 위해, 치환 텍스트를 나타내는 데 아무것도 덧붙이지 않은 중괄호를 사용합니다.
완전한 Python 표현식 지원
python-ideas 논의에 참여한 많은 사람은 단일 식별자만 지원하거나, 파이썬 표현식의 제한된 부분 집합(예: str.format()이 지원하는 부분 집합)만 지원하기를 원했습니다. 이 PEP는 중괄호 안에서 완전한 Python 표현식을 지원합니다. 완전한 표현식을 지원하지 않으면 바람직한 일부 사용 방식이 번거로워집니다. 예를 들어:
>>> f'Column={col_idx+1}'
>>> f'number of items: {len(items)}'
다음과 같이 됩니다:
>>> col_number = col_idx+1
>>> f'Column={col_number}'
>>> n_items = len(items)
>>> f'number of items: {n_items}'
f-문자열에 매우 보기 흉한 표현식이 포함될 수 있는 것은 사실이지만, 이 PEP는 그러한 사용을 린터나 코드 리뷰에서 다루어야 한다는 입장을 취합니다:
>>> f'mapping is { {a:b for (a, b) in ((1, 2), (3, 4))} }'
'mapping is {1: 2, 3: 4}'
다른 언어의 유사한 지원
위키백과에는 다른 프로그래밍 언어의 문자열 보간에 관한 좋은 논의가 있습니다 [9]. 이 기능은 다양한 문법과 제약을 가지고 많은 언어에 구현되어 있습니다.
f-문자열 표현식과 str.format 표현식의 차이점
str.format()에서 허용되는 제한된 표현식과 f-문자열 안에서 허용되는 완전한 표현식 사이에는 작은 차이가 하나 있습니다. 그 차이는 인덱스 조회가 수행되는 방식에 있습니다. str.format()에서는 숫자처럼 보이지 않는 인덱스 값이 문자열로 변환됩니다:
>>> d = {'a': 10, 'b': 20}
>>> 'a={d[a]}'.format(d=d)
'a=10'
딕셔너리에서 조회될 때 인덱스 값이 문자열 'a'로 변환된다는 점에 주목하십시오.
그러나 f-문자열에서는 'a' 값에 리터럴을 사용해야 합니다:
>>> f'a={d["a"]}'
'a=10'
이 차이가 필요한 이유는, 그렇지 않으면 변수를 인덱스 값으로 사용할 수 없기 때문입니다:
>>> a = 'b'
>>> f'a={d[a]}'
'a=20'
자세한 논의는 [10]을 참조하십시오. f-문자열에서 완전한 Python 표현식을 지원하게 된 것은 바로 이 관찰 때문입니다.
게다가 str.format()이 이해하는 제한된 표현식은 유효한 Python 표현식이 아니어도 됩니다. 예를 들어:
>>> '{i[";]}'.format(i={'";':4})
'4'
이러한 이유로 str.format()의 “표현식 파서”는 f-문자열을 구현하는 데 사용하기에 적합하지 않습니다.
삼중 따옴표 f-문자열
삼중 따옴표 f-문자열을 사용할 수 있습니다. 이러한 문자열은 일반 삼중 따옴표 문자열과 똑같이 파싱됩니다. 파싱과 디코딩이 끝나면 일반적인 f-문자열 로직이 적용되고, 각 값에 대해 __format__()이 호출됩니다.
원시 f-문자열
원시 문자열과 f-문자열은 결합할 수 있습니다. 예를 들어, 정규 표현식을 구성하는 데 사용할 수 있습니다:
>>> header = 'Subject'
>>> fr'{header}:\s+'
'Subject:\\s+'
또한 원시 f-문자열은 삼중 따옴표 문자열과 결합할 수 있습니다.
바이너리 f-문자열은 지원하지 않음
bytes.format()을 지원하지 않는 것과 같은 이유로, 'f'와 'b' 문자열 리터럴은 결합할 수 없습니다. 주된 문제는 객체의 __format__() 메서드가 바이트 문자열과 호환되지 않는 유니코드 데이터를 반환할 수 있다는 점입니다.
바이너리 f-문자열을 도입하려면 먼저 bytes.format()에 대한 해결책이 필요합니다. 이 아이디어는 과거에도 제안된 적이 있으며, 가장 최근에는 PEP 461에서 제안되었습니다. 이러한 기능에 대한 논의에서는 보통 다음 둘 중 하나를 제안합니다.
- 객체가 바이트로 변환되는 방식을 제어할 수 있도록
__bformat__()같은 메서드를 추가하는 것, 또는 bytes.format()을str.format()만큼 범용적이거나 확장 가능하지 않게 만드는 것입니다.
이러한 기능이 필요해진다면, 두 가지 모두 향후 선택지로 남아 있습니다.
!s, !r, !a는 중복됩니다
!s, !r, !a 변환이 꼭 필요한 것은 아닙니다. f-문자열 안에서는 임의의 표현식을 사용할 수 있으므로, 다음 코드는:
>>> a = 'some string'
>>> f'{a!r}'
"'some string'"
다음과 동일합니다.:
>>> f'{repr(a)}'
"'some string'"
마찬가지로, !s는 str() 호출로, !a는 ascii() 호출로 대체할 수 있습니다.
그러나 이 PEP는 str.format()과의 차이를 최소화하기 위해 !s, !r, !a를 지원합니다. str.format()은 임의의 표현식 실행을 허용하지 않으므로 str.format()에서는 !s, !r, !a가 필요합니다.
표현식 안의 람다
람다는 ':' 문자를 사용하므로, 표현식에서 괄호 바깥에 나타날 수 없습니다. 콜론은 형식 지정자의 시작으로 해석되므로, 람다 표현식의 시작 부분만 보이게 되어 문법적으로 유효하지 않게 됩니다. f-문자열 표현식에서 괄호로 감싸지 않은 람다를 실제로 사용할 일은 없으므로, 이는 큰 제약으로 여겨지지 않습니다.
람다를 꼭 사용해야 한다면, 괄호 안에서 사용할 수 있습니다.:
>>> f'{(lambda x: x*2)(3)}'
'6'
‘u’와 결합할 수 없음
‘u’ 접두사는 Python 2.7과의 소스 호환성을 쉽게 하기 위한 수단으로 PEP 414에 의해 Python 3.3에 추가되었습니다. Python 2.7은 앞으로도 f-문자열을 지원하지 않을 것이므로, ‘f’ 접두사를 ‘u’와 결합할 수 있게 해서 얻을 수 있는 것은 없습니다.
Python 소스 코드의 예
다음은 현재 str.format()을 사용하는 Python 소스 코드의 몇 가지 예와, 이를 f-문자열로 작성하면 어떤 모습이 되는지를 보여 줍니다. 이 PEP는 f-문자열로 일괄 변환할 것을 권장하지 않습니다. 이는 단지 str.format()이 실제로 사용되는 예와, 처음부터 f-문자열로 작성했다면 어떤 모습일지를 보여 주는 예일 뿐입니다.
Lib/asyncio/locks.py:
extra = '{},waiters:{}'.format(extra, len(self._waiters))
extra = f'{extra},waiters:{len(self._waiters)}'
Lib/configparser.py:
message.append(" [line {0:2d}]".format(lineno))
message.append(f" [line {lineno:2d}]")
Tools/clinic/clinic.py:
methoddef_name = "{}_METHODDEF".format(c_basename.upper())
methoddef_name = f"{c_basename.upper()}_METHODDEF"
python-config.py:
print("Usage: {0} [{1}]".format(sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), file=sys.stderr)
print(f"Usage: {sys.argv[0]} [{'|'.join('--'+opt for opt in valid_opts)}]", file=sys.stderr)
참고 문헌
Copyright
This document has been placed in the public domain.