무적/불사의 상징 아킬레우스
f-string은 파이썬 3.6버전 이후에 등장한 '비교적(?) 최신의 문자열 포매팅기법'입니다. 포매팅 방식 중에서는 누가뭐래도 가장 Pythonic한 formatting 방식이기도 합니다. 당연히 기존 문법과 다른 여러 장점들을 가지고 있습니다. 하지만, 문득 이런 생각이 들곤 합니다.
"포매팅을 써야할 상황이 되면 무조건 f-string을 쓰는 것이 좋을까?"
"최신의 기법이 가장 완벽한 대안일까?"
최신의 기법이나 제품이 항상 옳은 답이 아니라는건 여러 사례를 통해 증명되어왔습니다. 일례로 마이크로소프트사의 희대의 망작(?) '윈도우 ME'가 있겠네요. 이제는 2000년대 이후의 학생들은 생소할 이 망작은 윈도우 98과 XP사이에 나왔다가 정말 빠르게 소리소문 없이 사라진 희대의 윈도우 시리즈였습니다.
메모리 누수가 너무 심각해서 실행한지 일정 시간이 지나면 잦은 버퍼현상이 무조건적으로 발생해 재부팅을 필수로 해야했던 운영체제였죠. 버전업이 됬는데 전작보다 램 용량은 더 줄어서 출시한 갤럭시 노트20, 이거는 너무 많아 일일히 사례들기도 힘든 전작만도 못한 영화속편 등... 항상 최신의 기법의 최선의 결과만 가져오는것은 '아니다'라는건 우리는 이미 알고있습니다.
파이썬은 세 가지 문자열 포매팅 방식을 지원합니다.
파이썬이 처음 릴리즈 될 때부터 지원되었던 c언어와 유사한 % operator
방식
파이썬3에서 처음 등장했던 str.format
방식
그리고 가장 최근(3.6버전 이후)에 등장한 f-string
방식입니다.
간단한 예제코드와 함께 어떻게 사용될 수 있는지 살펴 보도록 합시다.
name = 'Tom'
age = 30
ex1 = 'Hi! my name is %s and I am %i' % (name, age) #%방식
ex2 = 'Hi! my name is {} and I am {}'.format(name, age) #str.format
ex3 = f'Hi! my name is {name} and I am {age}' #f-string
print(ex1)
print(ex2)
print(ex3)
# 세 가지 모두 결과 동일
# Hi! my name is Tom and I am 30
%방식
은 어떤 type인지 정확히 명시해줘야 합니다.
스트링이면 %s 정수형이면 %i 소수점도 포함된 실수형태면 %f 으로요.
str.format
따로 타입을 명시해주지 않아도 포매팅에 반영을 해줍니다. 하지만 두 방식보다는 f하나만 넣어주고 중괄호를 표시한 자리에 직접 변수를 때려박아 표현하는 f-string
이 훨씬 직관적이고 깔끔해보입니다.
특히 이런식으로 독스트링(triple-quotes)을 쓸만큼 포매팅할 문자열의 길이가 길어질수록 f-string
의 직관성이 훨씬 돋보입니다.
f'''
{f_name}은 파이썬 3.6버전 이후에 등장한 '비교적(?) 최신의 문자열 {kind}기법'입니다.
{kind} 방식 중에서는 누가뭐래도 가장 {expression}한 {kind} 방식이기도 합니다.
당연히 기존 문법과 다른 여러 장점들을 가지고 있습니다. 하지만, 문득 이런 생각이 들곤 합니다.
"{kind}을 써야할 상황이 되면 무조건 {f_name}을 쓰는 것이 좋을까?"
"최신의 기법이 가장 완벽한 대안일까?"
'''
변수명이 무슨 의미였는지 한 번 맞춰보세요...
첫째. 정수끼리 산술 연산을 지원합니다.
a = 10
b = 20
sum_result = f'sum result is {a+b}'
print(sum_result)
# 결과
sum result is 30
둘째. f-string선언 후 변수를 나중에 선언하는 형식도 지원합니다.
greeting = f'Hi! I am {name}'
name = 'Peter'
print(greeting)
# 결과
Hi! I am Peter
셋째. 성능적인 이점이 존재합니다.
timeit 모듈을 활용해 실행시간을 측정해보겠습니다.
import timeit
a = timeit.timeit("""name="Tom"
age = 74
'%s is %iyears old' % (name, age) """, number=100000)
b = timeit.timeit("""name="Tom"
age = 74
'{} is {}years old'.format(name, age) """, number=100000)
c = timeit.timeit("""name="Tom"
age = 74
'{name} is {age}years old'""", number=100000)
print(f"operator방식의 소요시간 : {round(a,4)}, str.format의 소요시간 : {round(b,4)}, f-string의 소요시간 : {round(c,4)}")
# 결과
operator방식의 소요시간 : 0.0243, str.format의 소요시간 : 0.0383, f-string의 소요시간 : 0.0015
조금이 아니라 생각보다 꽤 많이 차이가 납니다.f-string
은 다른 포매팅 방식들과 달리 상수와 함수를 사용하는것이 아닌 런타임 단계에서 사용되는 표현식이기 때문입니다.
아니 그럼 가시성도 좋고, 성능도 좋고, 더 많은 기능을 지원하는 f-string
그냥 쓰면 되는거 아니야? 라는 질문이 있을텐데, 아닙니다. f-string
역시 단점이 존재합니다.
아킬레우스도 발 뒷꿈치(아킬레우스건)이라는 약점이 존재하듯, f-string 역시 약점이 존재하는 포매팅방식입니다.
첫째. 변수명이 길고 문자열이 짧은 경우
샘플의 경우에는 변수명이 name
이나 age
같이 무척 짧았습니다.
하지만 반대로 변수명이 길고 문자열이 짧다면 어떨까요?
#str.format
'{}>{}>{}'.format(
diet_info_list['experiment_result'].get('1day_weight_loss'),
diet_info_list['experiment_result'].get('3day_weight_loss'),
diet_info_list['experiment_result'].get('total_weight_loss')
)
#f-string
f'{diet_info_list['experiment_result'].get('1day_weight_loss')}>{diet_info_list['experiment_result'].get('3day_weight_loss')}>{diet_info_list['experiment_result'].get('total_weight_loss')}'
자료구조가 복잡한 객체의 경우 데이터를 가공할 때 뜻하지 않게 변수명이 무척 길어지는 경우가 발생합니다. 이런 경우 라인 관리가 힘든 f-string
의 경우 가독성이 오히려 떨어져 보입니다.
가독성을 위해 독스트링을 써서 문자열을 한 칸씩 내리자니 출력값이 달라져버리죠. 결론적으로 이렇게 변수명이 길어질 때는 str.format
을 쓰는게 낫습니다.
두번째. 중괄호( {} )가 중첩되어 있는 문장
예를 들어 다음과 같이 다수의 중괄호가 중복/중첩되어 있는 문장이 있고, 이를 포매팅 해야된다고 생각합시다.
"""{'filters': [
{'filter': {
'value': {
'type': 'exact', 'value': 'invalid'},
'operator': 'enum_is'},
'property': 'seq3'},
{'filter': {
'operator': 'enum_is',
'value': {'type': 'exact', 'value': {이곳을 포매팅해야됨}}},
'property': 'h345'}
],
'operator': 'and'}"""
거두절미하고 결론만 말하겠습니다.
이런 문장에서는 f-string
str.format
둘 다 안됩니다. 억지로 하려고하면 방법이야 있지만 복잡하고 무척이나 번거롭습니다. 이런 문장에서 유일하게 가능한 방법은 가장 원시적인 방식인 % formatting입니다.
# 유일한 해결법
value_name = 'valid'
"""{'filters': [
{'filter': {
'value': {
'type': 'exact', 'value': 'invalid'},
'operator': 'enum_is'},
'property': 'seq3'},
{'filter': {
'operator': 'enum_is',
'value': {'type': 'exact', 'value': %s}},
'property': 'h345'}
],
'operator': 'and'}""" % (value_name)
왁.. 제가 얼마 전에 시작한 고민을 이 글 하나로 끝냈습니다..!! 깔끔한 내용정리 감사합니다!!!