프로그래밍/Python

f-strings (Literal String Interpolation)

감귤인건가 2022. 1. 7. 19:47

1. f-strings 소개

f-strings는 2015년 PEP-498에서 처음 제안되어 Python3.6부터 제공되기 시작한 기능입니다. 실제 제안 문서에서는 Literal String Interpolation이라는 이름으로 소개하고 있지만 일반적으로 f-string(formatted string의 줄임말)로 표현합니다.

f-strings는 문자열 포맷팅을 위해 사용되는 문법으로 자바스크립트의 Template Literal과 유사한 모습을 가지고 있습니다. 기존 Python2에서 사용하던 % 또는 Python3의 str.format 을 이용한 포맷팅에 비해 빠른 처리속도와 높은 코드 가독성을 제공한다는 점에서 현시점에서 문자열 포맷팅을 위한 가장 좋은 문법이 아닐까 생각됩니다.

간단한 사용 예시는 다음과 같습니다.

name = 'Alice'
age = 20

print(f'Hello my name is {name}. I am {age} years old')

[실행결과]

Hello my name is Alice. I am 20 years old

위의 예시에서 볼 수 있듯이 {} 문자 사이에 유효한 expression을 끼워넣어 간결하게 문자열 포맷팅을 할 수 있다는 것이 가장 큰 장점입니다. 이어서 f-strings의 다양한 활용을 살펴보도록 하겠습니다.


2. Expression Evaluation

f-string{} 문자 사이에는 유효한 expression이 들어갈 수 있다고 했습니다. 다시 말해 Python 코드를 삽입(embed)할 수 있다고 봐도 무방합니다. 삽입된 expression은 해당 식을 포함하고 있는 f-string과 같은 context를 가지므로 local 및 global 변수에 접근할 수 있습니다.


2-1. 계산식

계산식은 가장 일반적인 형태의 표현식(expression)으로 주로 간단한 덧셈 또는 곱셈의 결과를 출력하기 위해 많이 사용됩니다.

# 상수끼리의 덧셈을 수행합니다
print(f'10 + 20 = {10 + 20}')

# 변수를 활용할 수도 있습니다
a, b = 10, 20
print(f'{a} + {b} = {a + b}')

[실행결과]

10 + 20 = 30
10 + 20 = 30

 

2-2. 함수의 반환값

단순히 상수 또는 변수를 활용하는 것을 넘어 함수를 직접 호출하여 그 반환값을 출력하는 것도 가능합니다. 이것 또한 단순히 하나의 표현식이므로 당연한 것이지만 f-string을 처음 접하는 분들께 일반적으로 많이 사용되는 예시를 보여주기 위해 이를 간단히 기술하였습니다.

def mod(a, b):
    return a % b

a, b = 10, 3
print(f'{a} is divided by {b} with a remainder of {mod(a, b)}')

[실행결과]

10 is divided by 3 with a remainder of 1

3. Format Specifier

{} 안의 expression 뒤에 선택적으로 : 문자를 입력한 뒤 원하는 서식지정자(Format Specifier)를 선택할 수 있습니다. 이제 f-string의 형태를 다음과 같이 정리할 수 있습니다.

f'<text> { <expression> <: format_specifier> } <text>'

사용 예시를 살펴봄으로써 조금 더 쉽게 이해가능합니다.

a, b = 10, 3
print(f'{a} divided by {b} is {(a / b):.4f}')

[실행결과]

10 divided by 3 is approximately 3.3333

위의 예시는 float 자료형의 데이터를 소수점 아래 유효 4자리까지 출력하도록 한 것입니다. 이처럼 앞에 오는 각 데이터의 자료형에 맞는 서식형태를 임의로 사용할 수 있습니다. float와 더불어 많이 사용되는 것 중 datetime 자료형이 있습니다. 다음 예시는 현재 년월일을 출력하는 코드를 보여줍니다.

from datetime import datetime

if __name__ == '__main__':
    
    print(f'Today is {datetime.now():%Y.%m.%d}')

[실행 결과]

Today is 2022.01.05

한가지 알아두면 재미있는 사실은 서식지정자 자체도 {}를 이용한 evaluation을 통해 생성할 수 있다는 점입니다. 다음 예시는 float 자료형의 서식형태 중 width와 precision을 표현식 형태로 전달하는 것을 보입니다.

width, precision = 8, 4
print(f'10 divided by 3 : {(10 / 3):{width}.{precision}}')
print(f'10 divided by 7 : {(10 / 7):{width}.{precision}}')

[실행 결과]

10 divided by 3 :    3.333
10 divided by 7 :    1.429

{} 내부에 {}중첩하여 사용함으로써 서식지정자 자체를 표현식을 통해 생성할 수 있음을 보였습니다.


4. Self-Documenting Expressions

Self-Documenting 이란 f-string의 표현식에 = 문자를 붙여 표현식 자체와 그 evaluation 값을 모두 출력해주는 확장기능으로써 Python3.8에서 이 기능이 새롭게 추가되었습니다. 이 기능은 주로 디버깅을 하는 상황에서 유용하게 사용할 수 있습니다.

name, age = 'Alice', 20
print(f'{name=}, {age=}')

[실행 결과]

name='Alice', age=20

표현식에 해당하는 텍스트와 값이 모두 출력되는 모습을 확인할 수 있습니다. = 문자 이후 format specifier를 추가하여 사용하는 것또한 가능합니다. 참고로 = 주변에 공백문자를 입력할 경우 출력결과에서도 해당 공백이 그대로 반영되는 것을 확인할 수 있습니다.

num = 10 / 3
print(f'{num = :.2f}')

[실행 결과]

num = 3.33

다음과 같이 함수를 호출하여 그 반환값을 출력하고자 하는 경우에도 유용하게 사용할 수 있습니다.

def add(a, b):
    return a + b

print(f'{add(10, 20) = }')

[실행 결과]

add(10, 20) = 30

이와 같은 코드는 가독성의 측면에서 직관적인 형태의 문법은 아닐 수 있지만 익숙해진다면 빠르게 특정 expression의 결과값을 확인하기에 좋은 수단으로 사용할 수 있습니다.


5. Padding & Alignment

f-strings의 기능중 잘 사용하지는 않지만 출력 결과를 정렬하거나 특정 길이의 padding 문자열을 임의로 붙일 수 있는 기능이 존재합니다. 엄밀히 말하면 이것또한 서식 지정자(format specifier)의 한 종류입니다. 아래의 예시를 통해 해당 기능을 쉽게 이해할 수 있습니다.

x = 'hello'

print(f'{x:>11}')   # 우측정렬 (width : 11)
print(f'{x:&>11}')  # 우측정렬 + 패딩 문자(&) 추가
print(f'{x:*<11}')  # 좌측정렬 + 패딩 문자(*) 추가
print(f'{x:=^11}')  # 가운데 정렬 + 패딩 문자(=) 추가

[실행 결과]

      hello
&&&&&&hello
hello******
===hello===

패딩 문자를 임의로 지정해주지 않은 경우 공백(' ')문자로 설정되는 모습을 확인할 수 있습니다. 특정 상황에 따라 잘 활용할 수 있을 것으로 생각되지만 역시 가독성 측면에서 아쉬운 점이 있는 것 같습니다.


6. Conversions

Conversion은 f-string 내의 expression의 결과값을 포맷팅하기 전 어떤 형식으로 제공할 것인지를 결정하는 역할을 담당합니다. expression 이후 ! 문자를 추가한 뒤 s를 입력한 경우 표현식의 결과 값의 str()를, rrepr()을, aascii()를 적용한 결과를 반환합니다. 아래 예시를 통해 조금 더 수월하게 이해가 가능합니다.

x = '😃'

print(f'{x!r}') # repr() 호출
print(f'{x!s}') # str() 호출
print(f'{x!a}') # ascii() 호출

[실행 결과]

'😃'
😃
'\U0001f603'

7. Escaping

Escaping은 실제로 f-string을 사용하면서 가장 많이 겪게 되는 문제 중 하나입니다. 이 글에서는 대표적인 예시 몇 가지를 설명하고자 합니다.


7-1. 따옴표(quote) 출력

따옴표 문자로 둘러싸인 문자열(quoted string)을 f-string을 이용해 출력하는 경우 크게 두 가지 방법을 사용할 수 있습니다. expression 부()가 아닌 text 영역에서 \(backslash) 문자를 사용하거나, f-string 자체를 감싸고 있는 따옴표와 다른 quote 문자(single quote인 경우 double, 반대의 경우 single)를 사용할 수 있습니다.

x = 'quoted_string'

print(f'\'{x}\'')  # \(backslash) 문자를 사용하여 단일 따옴표 escape
print(f'"{x}"')    # 단일 따옴표로 싸여진 f-string내 쌍따옴표를 사용

[실행 결과]

'quoted_string'
"quoted_string"

 

7-2. 중괄호 문자('{') 출력

중괄호 문자 자체를 출력해야 하는 경우도 자주 발생합니다. 이런 경우 중괄호 문자를 중복하여 입력하면 됩니다.

print(f'{{Hello World!}}')

[실행 결과]

{Hello World!}

 

7-3. dict 객체의 멤버 출력

dict 객체의 멤버를 출력하기 위해 따옴표 문자를 사용해야 합니다. 이 경우 앞에서와 유사하게 f-string을 감싸고 있는 따옴표와 다른 종류의 따옴표 문자를 사용함으로써 escaping 할 수 있습니다.

price_of = {
    'Apple': 3000,
    'Banana': 4000,
    'Kiwi': 2500
}

print(f'{price_of["Kiwi"] = }')

[실행 결과]

price_of["Kiwi"] = 2500

마치며

이 글에서 미처 설명하지 못한 f-string의 디테일한 기능이 더 많이 있습니다. 해당 내용은 PEP-498 또는 이 문서를 참고하시면 좋을 것 같습니다.

Python의 기존 문법에 익숙하신 분들이 f-string 대신 기존의 % 또는 format 메소드를 사용하는 경우가 아직 많은 것 같습니다. 그럼에도 불구하고 현시점에서 f-string은 높은 가독성과 좋은 성능(빠른 속도)을 제공하기 때문에 사용하지 않을 이유가 크게 없다고 생각합니다. 익숙해진다면 상당히 유용하게 활용하게 되는 기능이므로 만약 아직 f-string과 친하지 않다면 조금씩 사용해보면서 감을 익히는 것을 권장합니다 :)