C스타일 형식 문자열을 str.format과 쓰기보다는 f-문자열을 통한 인터폴레이션을 사용하라

형식화(Formatting)는 미리 정의된 문자열에 데이터 값을 끼워 넣어서 완성된 문자열을 만드는 것!

name = '아이펠'
print('안녕하세요! 저는 %s입니다.' % name)
# 안녕하세요! 저는 아이펠입니다.

'저는 000입니다.'에서 000에 들어가는 문자를 따로 %형식자를 이용해 완성된 문자열을 만들게 된다. 하지만 % 연산자를 사용하게 되면 다양한 단점이 발생한다.

C스타일 형식 문자열의 단점

  1. tuple 내 데이터 값의 순서가 바뀌면 오류가 발생
key = 'my_var'
value = 1.234
print('%s = %f' %(key, value))

# key와 value의 순서를 바꾸면?
print('%s = %f' %(value, key))
>>> 
TypeError: must be real number, not str # value는 float, key는 str
  1. 형식화 전에 값을 변경해야 할 경우 식을 읽기가 매우 어려워진다.
pantry = [
('아보카도', 1.25),
('바나나', 2.5),
('체리', 15),
]

for i, (item, count) in enumerate(pantry):
	print('#%d: %-10s = %.2f' % (i, item, count))

#위의 for문 안의 튜플에는 짧아서 괜찮지만 값을 조절하기 위해 
#수식을 추가할 경우 tuple의 길이가 너무 길어져서 여러 줄에 나눠 써야하고 
#이로 인해 가독성이 나빠진다.

for i, (item, count) in enumerate(pantry):
	print('#%d: %-10s = %d' % (
		i + 1,
		item.title(),
		round(count)))

>>>
#0: 아보카도     = 1.25
#1: 바나나     = 2.50
#2: 체리     = 15.00

#1: 아보카도     = 1
#2: 바나나     = 2
#3: 체리     = 15
  1. 형식화 문자열에서 같은 값을 여러 번 사용하기 위해서는 튜플에서 같은 값을 여러번 사용해야 한다.
template = '%s는 음식을 좋아해. %s가 요리하는 모습을 봐요.'
name = '철수'
formatted = template % (name, name) #이렇게 name을 여러번 반복해야 한다.
print(formatted)

name = 'bruno'
formatted = template % (name.title(), name.title()) 
#이 때, title 메서드 호출을 잊어버릴 수도 있다.
print(formatted)

>>>
철수는 음식을 좋아해. 철수가 요리하는 모습을 봐
  1. 형식화 식에 딕셔너리를 사용하면 문장이 번잡스러워 진다.
for i, (item, count) in enumerate(pantry):
    before = '#%d: %-10s = %d' % (
        i + 1,
        item.title(),
        round(count))
    after = '#%(loop)d: %(item)-10s = %(count)d' % {
        'loop': i + 1,
        'item': item.title(),
        'count': round(count),
    }
# 각 키를 최소 두 번(형식지정자, 딕셔너리 키, [변수 이름]) 이상 사용하게 된다.
    
    assert before == after

해결방법

  1. 파이썬의 %연산자에 튜플 대신 딕셔너리를 사용해 형식화한다.
key = 'my_var'
value = 1.234

old_way = '%-10s = %.2f' % (key, value)

new_way = '%(key)-10s = %(value).2f' % {
'key' : key, 'value' : value}

→ 문제점 1, 문제점 3 은 해결할 수 있지만 문제점 4를 유발한다.

  1. 내장함수 format과 str.format
key = 'my_var'
value = 1.234

formatted = ' {} = {}'.format(key,value)
#format 메서드에 전달된 인자 중 순서상 같은 위치에 있는 위치 지정자에 들어간다.
print(formatted)
formatted = '{1} = {0}'.format(key, value)
print(formatted)

>>>
1.234 = my_var

→ format에 넘기는 인자의 순서를 바꾸지 않아도 형식화 문자열을 사용해 형식화한 값의 출력 순서를 변경할 수 있다.

→ 문제점 1 해결

formatted = '{0}는 음식을 좋아해. {0}가 요리하는 모습을 봐요.'.format(name)
print(formatted)

>>>
철수는 음식을 좋아해. 철수가 요리하는 모습을 봐요.

→ 형식화 문자열 안에서 위치 인덱스를 여러번 활용할 수 있어 format메서드에 값을 여러번 반복할 필요가 없다.

→ 문제점 3 해결

for i, (item, count) in enumerate(pantry):
	old_style = '#%d: %-10s = %d' % (
		i + 1,
		item.title(),
		round(count))

	new_style = '#{}: {:<10s} = {}'.format(
		i + 1,
		item.title(),
		round(count))

	assert old_style == new_style

→ 형식화를 하기 전에 값을 변경해야 하는 경우에는 코드 읽기가 어려워진다. C 스타일 형식화와 새로운 형식화는 가독성 면에서 차이가 없다.

→ 문제점 2 해결 불가

old_template = (
    'Today\'s soup is %(soup)s, '
    'buy one get two %(oyster)s oysters, '
    'and our special entrée is %(special)s.')
old_formatted = template % {
    'soup': 'lentil',
    'oyster': 'tongyoung',
    'special': 'schnitzel',
}

new_template = (
    'Today\'s soup is {soup}, '
    'buy one get two {oyster} oysters, '
    'and our special entrée is {special}.')
new_formatted = new_template.format(
    soup='lentil',
    oyster='tongyoung',
    special='schnitzel',
)

assert old_formatted == new_formatted

→ 딕셔너리에 미해 작은 따옴표 등이 덜 사용되긴 하지만 여전히 키가 반복되는 경우의 중복을 줄여주지는 못한다.

→ 문제점 4 해결 불가

최종 해결방법

위와 같이 해결방법으로 제시된 딕셔너리를 사용하는 방법이나 format을 사용하는 방법은 해결하지 못하는 문제점이 존재했다. 이를 해결하기 위해 인터폴레이션을 통한 형식 문자열(f-문자열)이 도입됐다.

→ 형식 문자열 앞에 f문자를 붙인다.

  • 장점
    1. 형식 문자열의 표현력을 극대화 한다.
    2. 형식화 문자열에서 키와 값을 불필요하게 중복 지정해야 하는 경우를 없애준다.
    3. 형식화 식 안에서 현재 파이썬 영역에서 사용할 수 있는 모든 이름을 자유롭게 참조할 수 있도록 허용함으로써 간결함을 제공한다.
key = 'my_var'
value = 1.234

formatted = f'{key} = {value}'
print(formatted)

>>>
my_var = 1.234

→ 데이터의 순서나 타입이 바뀌더라도 오류가 발생하지 않는다.

→ 문제점 1 해결

formatted = f'{key!r:<10} = {value:.2f}'
print(formatted)

>>>
'my_var' = 1.23

→ format함수의 형식 지정자 안에서 콜론 뒤에 사용할 수 있는 내장 미니 언어를 사용할 수 있다.

→ 값을 유니코드나 repr 문자열로 변환하는 기능 역시 사용할 수 있다.

f_string = f'{key:<10} = {value:.2f}'

c_tuple = '%-10s = %.2f' % (key, value)

str_args = '{:<10} = {:.2f}'.format(key,value)

str_kw = '{key:<10} = {value:.2f}'.format(key=key,value=value)

c_dict = '%(key)-10s = %(value).2f % {'key': key, 'value': value}

assert c_tuple == c_dict == f_string
assert str_args == str_kw == f_string

→f-문자열을 사용한 형식화는 앞에서 살펴본 다른 방법에 비해 항상 더 짧다.

for i, (item, count) in enumerate(pantry):
	old_style = '#%d: %-10s = %d' % (
		i + 1,
		item.title(),
		round(count))

	new_style = '#{}: {:<10s} = {}'.format(
		i + 1,
		item.title(),
		round(count))

	f_string = f'#{i+1}: {item.title():<10s} = {round(count)}'

	assert old_style == new_style == f_string

→ f-문자열을 사용하면 위치 지정자 중괄호 안에 완전한 파이썬 식을 넣을 수 있다.

→ 값을 약간 변경하고 싶을 때도 간결한 구문으로 표기할 수 있다.

→ 문제점 2 해결

for i, (item, count) in enumerate(pantry):
	print(f'#{i+1}: '
	f'{item.title():<10s} = '
	f'{round(count)}')

>>>
#1: 아보카도    = 1
#2: 바나나    = 2
#3: 체리    = 15

→ 연속된 문자열을 서로 연결해주는 기능을 사용해 f-문자열을 여러 줄로 나눌 수 있다.

→ 한줄짜리 코드보다 길지만, 다른 방식에 비해 깔끔하다.

places = 3
number = 1.23456
print(f'내가 고른 숫자는 {number:.{places}f}')

>>>
내가 고른 숫자는 1.235

→ 파이썬 식을 형식 지정자 옵션에 넣을 수 있다.

결론

  • f-문자열은 표현력, 간결성, 명확성 부분에서 형식화 옵션 중 가장 뛰어나다.
  • 값을 문자열로 형식화해야 하는 상황을 만나게 되면 다른 대안 대신 f-문자열을 사용해라.

0개의 댓글