[파이썬 튜토리얼] 문자열 포매팅

f-string, placeholder와 format 메소드, numbered placeholder, named placeholder, 중괄호 escaping, f-string debug support, format specifier 등

·

11 min read

Level 1

터미널로 입력된 문자열의 앞에, input was: 라는 내용을 붙여 다시 출력해야 한다고 하자. 앞의 내용을 되짚어보기 위해, 입력된 값을 그대로 출력해주는 코드를 먼저 작성했다.

x = input()

print(x)

입력

hello

결과

hello

hello가 입력되었을 때 input was: hello라는 내용을 출력하기 위해서는, 터미널에서 입력된 내용을 담고 있는 변수 x의 앞에 문자열을 붙여줘야 한다. 다음처럼 문자열 내에 변수의 이름을 넣는 것만으로는 해결할 수 없다.

x = input()

print('input was: x')

입력

hello

결과

input was: x

이번 단원에서 소개할 문자열 포매팅을 통해 이 문제를 풀어낼 수 있다. 문자열 포매팅은 '이 자리에 무언가 값이 채워질 것임'을 명시하는 placeholder(자리 표시자)를 문자열 내에 포함시키고, 그 placeholder를 대체할 값을 명시하는 것이 기본적인 흐름이다.

파이썬은 문자열 포매팅을 지원하기 위한 기능을 지속적으로 추가해 왔다. 그러므로 문자열 포매팅을 위한 문법은 여러가지가 있다. 가장 최근에 추가된 f-string을 시작으로, 그보다 더 예전에 사용하던 방식들을 최신순으로 설명할 것이다. 다만 현재 Python 3.7이 최소 버전으로 권고되기 때문에, 가장 처음 소개할 f-string에 대해서만 읽고 지나가도 된다.

소프트웨어의 업데이트가 더 이상 이루어지지 않는 시점을 End Of Life(EOL)이라고 말한다. Windows XP나 Android 8(Oreo)을 예로 들 수 있다. 보안 업데이트에 한해 관리를 유지해주는 경우가 있어서, 보통은 보안 업데이트까지 지원 종료되는 시점을 EOL로 치곤 한다. endoflife.date에 잘 정리되어 있다. Python 3.6까지의 버전은 모두 EOL을 넘겼다. EOL을 넘긴 버전은 되도록 사용하지 않는 것이 좋다.

f-string

Python 3.6 버전에는 f-string이라는 기능이 추가되었다. 문자열을 시작하는 따옴표 앞에 f를 붙이고, 문자열 내에 포함시키고자 하는 내용을 중괄호로 감싸주면 된다.

x = 1
y = 2

print(f'x is {x}, y is {y}')

결과

x is 1, y is 2

f 뒤에 문자열(string)이 이어지기 때문에 f-string이라고 부른다. f-string임을 명시하는 문자 f는 대소문자에 관계 없으나, PEP 8은 소문자로 사용하는 것을 권고한다.

x = 1
y = 2

print(f'x is {x}, y is {y}')
print(F'x is {x}, y is {y}')

결과

x is 1, y is 2
x is 1, y is 2

이 내용은 PEP 498 - Literal String Interpolation에 명시되어 있다. Python 3.6 이상을 사용하고 있다면, f-string을 사용하는 것을 추천한다. 이전 세대의 포매팅 방식들보다 가독성과 자유도가 높고, 이와 비슷한 모양의 포매팅 방식이 다른 프로그래밍 언어에서도 꽤 사용되기 때문이다.

placeholder 표현식과 format

아직 class에 대한 내용을 다루지 않았기 때문에, 문자열의 format 메소드에 대해 메소드 대신 기능이라는 단어를 사용했다.

f-string이 없는 Python 3.6 미만 버전에서는, 문자열이 제공하는 format이라는 기능을 사용할 수 있다. 다음 순서대로 코드를 작성하면 된다.

  1. 문자열 내에 {}를 두어 placeholder를 표현한다.
  2. 문자열 뒤에 .format()을 붙이고, placeholder의 자리를 채울 값들을 소괄호 안에 순서대로 명시한다.
x = 1
y = 2

print('x is {}, y is {}'.format(x, y))

결과

x is 1, y is 2

'x is {}, y is {}'에 명시된 두 placeholder에 각각 xy에 해당하는 값이 대체되었다. format() 내에 명시한 순서대로 placeholder를 대체하는 것이다. 값이 placeholder의 개수보다 많이 전달되는 것은 괜찮지만, 부족하면 에러가 발생한다.

x = 1
y = 2

print('x is {}, y is {}'.format(x, y, 3, 4))
print('x is {}, y is {}'.format(x))

결과

x is 1, y is 2
Traceback (most recent call last):
  File "example.py", line 5, in <module>
    print('x is {}, y is {}'.format(x))
IndexError: Replacement index 1 out of range for positional args tuple

numbered placeholder

placeholder 표현식에는 {3}과 같이 순번을 명시할 수 있다. 이는 format() 내에 명시된 값들 중 해당 순서의 값을 placeholder에 사용하게 만든다.

순번은 0번부터 시작한다. {0}에는 format()에 첫 번째로 명시된 값을, {1}에는 두 번째로 명시된 값을 사용하는 식이다.

print('first value is {0}, second value is {1}'.format(1, 2))

결과

first value is 1, second value is 2

앞에서 먼저 설명했던, 순번이 설정되지 않은 placeholder 표현식({})에는 순서가 자동으로 부여된다. 0으로 시작해, 순서대로 1씩 증가하는 값이 사용된다. 예로 'x is {}, y is {}'라는 포맷 문자열은, 'x is {0}, y is {1}'과 동일한 의미다.

x = 1
y = 2

print('x is {}, y is {}'.format(x, y))
print('x is {0}, y is {1}'.format(x, y))

결과

x is 1, y is 2
x is 1, y is 2

순번을 순서대로 지정하지 않아도 되고, 동일한 순번을 가진 placeholder가 여러 번 등장해도 괜찮다.

x = 1
y = 2

print('x is {0}, y is {1}. x({0}) * y({1}) = {2}'.format(x, y, x * y))

결과

x is 1, y is 2. x(1) * y(2) = 2

named placeholder

numbered placeholder가 순번을 명시할 수 있었던 것처럼, named placeholder는 {x} 처럼 이름을 명시할 수 있다. 이 경우, placeholder에 사용된 이름에 맞게 format()에 값을 명시해 주어야 한다. 이름=값 형태로 작성하면 된다.

x = 1
y = 2

print('x is {a}, y is {b}'.format(a=x, b=y))

결과

x is 1, y is 2

조언

  • Python 3.6 버전 이상을 사용하는 파이썬 프로젝트에선 f-string 외의 문자열 포매팅 방식이 선호되지 않는다. 따라서 f-string만 잘 사용하면 된다. f-string을 제외한 다른 방식들은, 코드를 해석할 줄만 알면 넘어가도 무관하다. 튜토리얼에서는 문자열 포매팅이 필요한 경우 f-string만 사용할 것이다.

연습문제

결과 예상하기 1

다음 코드의 실행 결과를 예상해보자.

a = 0
b = 1

print(f'{a + 1} {b - 1}')
print(a)
print(b)

결과 예상하기 2

다음 코드의 실행 결과를 예상해보자.

x = input()

print('input value is {}'.format(x))

입력

3

결과 예상하기 3

다음 코드의 실행 결과를 예상해보자.

a = 0
b = 1

print('{1} {0}'.format(a, b))

결과 예상하기 4

다음 코드의 실행 결과를 예상해보자.

print('{a} {abc} {a}'.format(a=1, b=3, abc=999))

Level 2

string interpolation

f-string처럼 문자열 내에 표현식을 내장(embed)시키는 것을 string interpolation이라고 한다. 각자 문법은 조금씩 다르지만, 많은 프로그래밍 언어들이 string interpolation을 지원한다. 다음은 JavaScript의 예다.

var x = 1;
var y = 2;

console.log(`x is ${x}, y is ${y}`);

결과

x is 1, y is 2

포매팅 후에 중괄호를 남기기

파이썬은 문자열을 포매팅해야할 때, 문자열 내에 중괄호가 등장하면 그 종류(여는 괄호, 닫는 괄호)에 관계 없이 placeholder로 해석하려고 한다. 따라서 다음과 같은 에러를 마주칠 때가 있다.

print(f'answer of 3*4} {3 * 4}')

결과

  File "example.py", line 1
    print(f"answer of 3*4} {3*4}")
                                 ^
SyntaxError: f-string: single '}' is not allowed

중괄호가 placeholder로 해석되지 않게 하려면, 동일한 종류의 중괄호를 한 번 더 붙여주면 된다.

print(f'answer of 3*4}} {3 * 4}')

결과

answer of 3*4} 12

f-string의 debug support

Python 3.8부터, f-string에 {name=}과 같은 표현식을 사용할 수 있게 되었다. {name=}name={name}와 동일한 효과를 갖는다.

x = 0

print(f'{x=}')

결과

x=0

등호 좌우의 공백은 포매팅 결과에 그대로 반영된다.

x = 0

print(f'{x =}')
print(f'{x= }')
print(f'{x = }')

결과

x =0
x= 0
x = 0

%

C언어의 printf, Go언어의 fmt.Printf 등에서는 %d, %s와 같이 % 기호에 타입을 나타내는 알파벳을 이어붙여 placeholder를 명시한다. 파이썬도 이러한 방식의 문자열 포매팅을 지원한다.

nickname = 'planb'

print('My nickname is %s.' % nickname)

결과

My nickname is planb.

format이나 f-string이 추가된 뒤로 이 방법은 선호되지 않아 'Level 1'에서 제외했다.

Level 3

placeholder에 속성 접근식 담기

placeholder 내에 표현식을 포함시킬 수 있다. 속성 접근, Mapping 참조, Indexing에 한정되지만 꽤 유용하다.

class Dummy:
    x = 10


obj = Dummy()

print('x is {.x}'.format(obj))
print('x is {0.x}'.format(obj))
print('x is {obj.x}'.format(obj=obj))

print('value is {[value]}'.format({'value': 0}))
print('value is {data[value]}'.format(data={'value': 0}))

print('first item is {[0]}'.format(['a', 'b', 'c']))
print('first item is {data[0]}'.format(data=['a', 'b', 'c']))

결과

x is 10
x is 10
x is 10
value is 0
value is 0
first item is a
first item is a

Mapping을 참조하는 경우, key에 따옴표를 감싸지 않는 것에 주의해야 한다. {data["value"]}가 아니라 {data[value]}처럼 작성해야 한다.

format specifier

placeholder의 표현식 뒤에 콜론(:)을 붙여서, 표현식의 값이 표시되는 방식을 지정할 수 있다. 예를 들어,

  • 150015처럼, 값이 네자릿수가 될 때까지 앞에 0을 붙인다.
  • 15+15처럼, 부호가 드러나게 한다.
  • 0.621562.2%처럼, 백분율로 표현하고 소수점 첫째 자리까지 반올림한다.
  • 432b처럼, 16진수로 변환한다.

다음 예제는 이 4가지를 표현한 것이다.

print(f'{15:0=4}')
print(f'{15:+}')
print(f'{0.6215:.1%}')
print(f'{43:x}')

결과

0015
+15
62.2%
2b

여기에 사용된 0=4, +f, .1%, x 부분이 format specifier다. 그러나, format specifier를 잘 모르는 상황에서는 그 모양만 보고 결과물을 예상하기는 어렵다. 문법이 다음처럼 매우 복잡하기 때문이다. 때문에 format specifier를 mini language라고 부르기도 한다.

[[fill]align][sign][#][0][minimumwidth][.precision][type]

맨 뒤에 있는 type으로 시작해, 앞으로 가면서 하나씩 알아보도록 하겠다.

[type]

type은 10진수 값을 2진수로 표현하는 등, 데이터 표시 방식을 설정하는 데에 사용할 수 있다. 다음은 정수 타입의 값을 대상으로 하는 specifier들을 나열한 예제다. 대화형 인터프리터 형태로 구성했다.

>>> f'{78:b}'
'1001110'
>>> f'{78:o}'
'116'
>>> f'{78:x}'
'4e'
>>> f'{78:X}'
'4E'
>>> f'{78:c}'
'N'
  • b : 2진수로 변환
  • o : 8진수로 변환
  • x : 16진수로 변환, 소문자 사용
  • X : 16진수로 변환, 대문자 사용
  • c : 숫자에 해당하는 유니코드 문자로 변환

다음은 숫자 타입의 값을 대상으로, 실수 표현 방식을 변경하는 specifier들을 나열한 예제다. 대화형 인터프리터 형태로 구성했다.

>>> f'{0.00003:e}'
'3.000000e-05'
>>> f'{float("inf"):e}'
'inf'
>>> f'{float("nan"):e}'
'nan'
>>> f'{0.00003:E}'
'3.000000E-05'
>>> f'{float("inf"):E}'
'INF'
>>> f'{float("nan"):E}'
'NAN'
>>> f'{0.00003:f}'
'0.000030'
>>> f'{0.00003:F}'
'0.000030'
>>> f'{0.00003:g}'
'3e-05'
>>> f'{0.00003:G}'
'3E-05'
>>> f'{0.00003:%}'
'0.003000%'
  • e : 지수 표기법으로 실수를 표현한다.
  • E : e 타입처럼 지수 표기법으로 실수를 표현한다. 지수를 e 대신 E로, nanNAN, infINF로 표현한다.
  • f : 고정 소수점으로 실수를 표현한다.
  • F : f 타입처럼 고정 소수점으로 실수를 표현한다. nanNAN, infINF로 표현한다.
  • g, G : 대상의 크기와 뒤에서 설명할 precision(정밀도)에 따라, e 타입과 f 타입 중 하나를 사용해 포매팅한다. precision을 다룬 뒤 재차 설명한다.
  • % : 대상에 100을 곱한 뒤, f 타입과 동일한 포매팅을 수행하고 뒤에 percent sign(%)을 붙인다. 백분율 표현을 위해 사용한다.

이 specifier들은 실수 타입의 결과를 만든다. 따라서 정수를 대상으로 사용하는 경우, float 함수를 거쳐 타입 캐스팅한 뒤 포매팅을 적용된다.

print(f'{15:e}')
print(f'{15:f}')
print(f'{15:%}')

결과

1.500000e+01
15.000000
1500.000000%

다음은 숫자 타입의 값을 대상으로, thousands separator를 적용하는 specifier들을 나열한 예제다.

print(f'{78000:n}')
print(f'{78000:,}')
print(f'{78000:_}')

결과

78000
78,000
78_000
  • n : 실행 환경의 locale 정보를 근거로 thousands separator를 적용한다(예제의 결과에는 드러나지 않음). 대상이 실수인 경우 그 전에 g type과 동일한 포매팅을 수행한다.
  • , : , 기호를 thousands separator로 사용한다. Python 3.1부터 사용할 수 있다.
  • _ : _ 기호를 thousands separator로 사용한다. Python 3.6부터 사용할 수 있다.

locale 모듈로 런타임에 locale 정보를 설정해, n type의 동작을 실험해볼 수 있다.

import locale

# locale 설정 전
print(f'{78000:n}')

# 숫자 포매팅에 대한 범주에 locale 설정
locale.setlocale(locale.LC_NUMERIC, 'ko_KR')

# locale 설정 후
print(f'{78000:n}')

결과

78000
78,000

문자열에 사용할 수 있는 s 타입이 있으나 무의미하다. 애초에 기본값이 s 타입이고, 그 외에 사용할 수 있는 것이 없기 때문이다.

print(f'{"abc":s}')
print(f'{"abc"}')

결과

abc
abc

[.precision]

precision은 문자열 또는 실수 타입을 대상으로 사용할 수 있다. 문자열에 대해, precision은 문자열의 최대 길이를 제한한다. 문자열 타입의 변수 s가 있을 때, f'{s:.2}'s[:2]와 동일한 의미다.

print(f'{"abc":.0}')
print(f'{"abc":.2}')
print(f'{"abc":.5}')

결과


ab
abc

실수에 대해, precision은 소수점 아래 자릿수를 제한한다. 반올림이 적용된다.

print(f'{0.113:.2}')
print(f'{0.113:.1}')
print(f'{0.17:.1}')

결과

0.11
0.1
0.2

다음은 지금까지 알아본 precision, type을 모두 사용한 예다.

print(f'{0.01394:.2%}')

결과

1.39%

[minimumwidth]

minimumwidth는 대상이 문자열 내에서 차지할 길이를 나타낸다. 다음은 'abc'라는 문자열이 길이 8만큼의 자리를 차지하게 만드는 포매팅의 예다. 자리를 차지하는 개념을 쉽게 알 수 있도록 앞뒤에 dot(.) 기호를 붙였다.

print(f'.{"abc":8}.')

결과

.abc     .

문자열 'abc'는 minimumwidth에 따라 길이 8만큼의 자리를 차지해야 하므로, 본인의 길이를 제외한 5칸을 공백으로 채웠다. 숫자의 경우 문자열과 다르게 오른쪽으로 정렬된다.

print(f'.{0.01:8}.')

결과

.    0.01.

문자열과 숫자의 기본 정렬 방향이 다른 이유는 뒤에서 알아볼 [[fill]align]을 통해 알 수 있다. 여기서 간단하게 먼저 이야기하면, 문자열은 기본적으로 좌측 정렬이, 숫자는 기본적으로 우측 정렬이 적용된다.

다음은 지금까지 알아본 minimumwidth, precision, type을 모두 사용한 예다.

print(f'{0.01394:8.2%}')

결과

   1.39%

[0]

숫자 타입의 값에 minimumwidth가 사용되는 경우, minimumwidth의 앞에 0을 붙여주면 zero padding을 할 수 있다.

print(f'{15:08}')

결과

00000015

이는 뒤에서 설명할 [[fill]align]과 어느 정도 비슷하다. [[fill]align]은 문자열이 차지할 width 내에서의 정렬 방향(align)과 빈 자리를 채울 값(fill)을 나타낸다. 다음은 {15:08}[[fill]align]으로 표현한 간단한 예다.

print(f'{15:0>8}')

결과

00000015

[[fill]align]과 다르게 [0]은 부호를 인식해 자연스럽게 zero padding을 수행한다.

print(f'{-15:0>8}')
print(f'{-15:08}')

결과

00000-15
-0000015

다음은 지금까지 알아본 0, minimumwidth, precision, type을 모두 사용한 예다.

print(f'{-0.01394:08.2%}')

결과

-001.39%

[#]

정수 타입의 값을 대상으로 사용할 수 있는 #typeb, o, x일 때 문자열의 앞에 각각 0b, 0o, 0x를 붙이는 역할을 한다.

print(f'{123:#b}')
print(f'{123:#o}')
print(f'{123:#x}')

결과

0b1111011
0o173
0x7b

다음은 지금까지 알아본 #, 0, minimumwidth, type을 사용한 예다. #은 정수 타입에 대해서만 사용할 수 있으므로, precision과는 혼용이 불가능해 제외했다.

print(f'{12:#08x}')

결과

0x00000c

[sign]

sign은 대상이 숫자 타입인 경우에만 사용할 수 있고, 유효한 값으로 +, -, (공백)이 있다. 기본값은 -다.

print(f'{15:+}')
print(f'{-15:+}')

print(f'{15:-}')
print(f'{-15:-}')

print(f'{15: }')
print(f'{-15: }')

결과

+15
-15
15
-15
 15
-15
  • + : 대상의 부호를 항상 표시
  • - : 대상이 음수일 때만 부호 표시
  • (공백) : 양수일 때 한 칸 공백을 삽입하고, 음수일 때 부호 표시

다음은 지금까지 알아본 sign, 0, minimumwidth, precision, type을 모두 사용한 예다. 타입 관련(#precision을 함께 사용할 수 없는) 문제로 #을 제외했다.

print(f'{0.01394:+08.2%}')

결과

+001.39%

[[fill]align]

format specifier의 가장 앞에는 [fill]align이 포함될 수 있다. align은 표현식이 차지해야 할 자리 내에서, 값이 문자열의 어느 방향으로 정렬되어야 하는지를 기호로 나타낸다.

align에는 사용할 수 있는 값이 <, >, ^, =로 정해져 있다. 각각의 의미는 예제를 통해 알아보도록 하자. 예제의 format specifier 마지막에 명시된 8minimumwidth로 해석된다. align이 되는 것을 확인하려면 대상이 충분한 공간을 차지해야 하기 때문에 추가했다.

print(f'.{"abc":<8}.')
print(f'.{"abc":>8}.')
print(f'.{"abc":^8}.')
print(f'.{15:=8}.')

결과

.abc     .
.     abc.
.  abc   .
.      15.

< 기호는 왼쪽으로 붙이도록, > 기호는 오른쪽으로 붙이도록, ^ 기호는 가운데로 정렬하도록, = 기호는 숫자 타입의 대상에 한해 오른쪽으로 붙이도록 만든다.

fill은 빈 자리를 어느 값으로 채울지를 나타낸다. [[fill]align]이라는 문법에 맞게, 단독으로 사용될 수 없으며 항상 align을 후행해야 한다.

print(f'{"abc":x<8}')
print(f'{"abc":x>8}')
print(f'{"abc":x^8}')
print(f'{15:x=8}')

결과

abcxxxxx
xxxxxabc
xxabcxxx
xxxxxx15

align의 기본값은 대상이 숫자 타입일 때 >, 그 외일 때 <가 사용된다. 따라서 숫자 타입을 대상으로 :8:>8은 동일한 의미이며, 그 외의 타입을 대상으로 :8:<8은 동일한 의미다.

print(f'.{15:8}.')
print(f'.{15:>8}.')

print(f'.{"abc":8}.')
print(f'.{"abc":<8}.')

결과

.      15.
.      15.
.abc     .
.abc     .