# 7. 입력과 출력

프로그램의 출력을 표현하는 여러 가지 방법이 있습니다; 사람이 일기에 적합한 형태로 데이터를 인쇄할 수도 있고, 나중에 사용하기 위해 파일에 쓸 수도 있습니다. 이 장에서는 몇 가지 가능성을 논합니다.

## 7.1. 장식적인 출력 포매팅

지금까지 우리는 값을 쓰는 두 가지 방법을 만났습니다: 표현식 문장 과 `print()` 함수입니다. (세 번째 방법은 파일 객체의 `write()` 메서드를 사용하는 것입니다; 표준 출력 파일은 `sys.stdout` 로 참조할 수 있습니다. 이것에 대한 자세한 정보는 라이브러리 레퍼런스를 보세요.)

종종 단순히 스페이스로 분리된 값들을 인쇄하기보다, 출력의 포맷을 좀 더 제어하고 싶기 마련입니다. 출력을 포매팅하는 두 가지 방법이 있습니다; 첫 번째 방법은 여러분 스스로 모든 문자열 처리를 하는 것입니다; 문자열 슬라이싱과 이어붙이기를 사용하면 여러분이 상상할 수 있는 어떤 배치라도 만들어 낼 수 있습니다. 문자열형은 문자열을 주어진 칼럼 폭으로 채워주는 편리한 연산들을 수행하는 메서드들을 제공합니다; 이것은 뒤에서 간단히 설명합니다. 두 번째 방법은 포맷 문자열 리터럴 이나 `str.format()` 메서드를 사용하는 것입니다.

`string` 모듈은 `Template` 클래스를 포함하는데, 값을 문자열에 치환하는 또 다른 방법을 제공합니다.

물론, 한가지 질문이 남아있습니다; 값을 어떻게 문자열로 변환하는가? 다행히도, 파이썬은 어떤 종류의 값이라도 문자열로 변환하는 방법을 갖고 있습니다; 그 값을 `repr()` 나 `str()` 함수로 전달하세요.

`str()` 함수는 어느 정도 사람이 읽기에 적합한 형태로 값의 표현을 돌려주게 되어있습니다. 반면에 `repr()` 은 인터프리터에 의해 읽힐 수 있는 형태를 만들게 되어있습니다 (또는 그렇게 표현할 수 있는 문법이 없으면 `SyntaxError` 를 일으키도록 구성됩니다). 사람이 소비하기 위한 특별한 표현이 없는 객체의 경우, `str()` 는 `repr()` 과 같은 값을 돌려줍니다. 많은 값, 숫자들이나 리스트와 딕셔너리와 같은 구조들, 은 두 함수를 쓸 때 같은 표현을 합니다. 특별히, 문자열은 두 가지 표현을 합니다.

몇 가지 예를 듭니다:

    >>> s = 'Hello, world.'
    >>> str(s)
    'Hello, world.'
    >>> repr(s)
    "'Hello, world.'"
    >>> str(1/7)
    '0.14285714285714285'
    >>> x = 10 * 3.25
    >>> y = 200 * 200
    >>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
    >>> print(s)
    The value of x is 32.5, and y is 40000...
    >>> # The repr() of a string adds string quotes and backslashes:
    ... hello = 'hello, world\n'
    >>> hellos = repr(hello)
    >>> print(hellos)
    'hello, world\n'
    >>> # The argument to repr() may be any Python object:
    ... repr((x, y, ('spam', 'eggs')))
    "(32.5, 40000, ('spam', 'eggs'))"

여기 제곱수와 세제곱수의 표를 쓰는 두 가지 방법이 있습니다:

    >>> for x in range(1, 11):
    ...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
    ...     # Note use of 'end' on previous line
    ...     print(repr(x*x*x).rjust(4))
    ...
     1   1    1
     2   4    8
     3   9   27
     4  16   64
     5  25  125
     6  36  216
     7  49  343
     8  64  512
     9  81  729
    10 100 1000

    >>> for x in range(1, 11):
    ...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
    ...
     1   1    1
     2   4    8
     3   9   27
     4  16   64
     5  25  125
     6  36  216
     7  49  343
     8  64  512
     9  81  729
    10 100 1000

(첫 번째 예에서, `print()` 의 동작 방식으로 인해 각 칼럼 사이에 스페이스 하나가 추가되었음에 유의하세요: 기본적으로 인자들 사이에 스페이스를 추가합니다.)

이 예는 문자열 객체의 `str.rjust()` 메서드를 시연하는데, 왼쪽에 스페이스를 채워서 주어진 폭으로 문자열을 우측 줄 맞춤합니다. 비슷한 메서드 `str.ljust()` 와 `str.center()` 도 있습니다. 이 메서드들은 어떤 것도 출력하지 않습니다, 단지 새 문자열을 돌려줍니다. 입력 문자열이 너무 길면, 자르지 않고, 변경 없이 그냥 돌려줍니다; 이것이 칼럼 배치를 엉망으로 만들겠지만, 보통 값에 대해 거짓말을 하게 될 대안보다는 낫습니다. (정말로 잘라내기를 원한다면, 항상 슬라이스 연산을 추가할 수 있습니다, `x.ljust(n)[:n]` 처럼.)

다른 메서드도 있습니다, `str.zfill()`. 숫자 문자열의 왼쪽에 0을 채웁니다. 플러스와 마이너스 부호도 이해합니다:

    >>> '12'.zfill(5)
    '00012'
    >>> '-3.14'.zfill(7)
    '-003.14'
    >>> '3.14159265359'.zfill(5)
    '3.14159265359'

`str.format()` 메서드의 기본적인 사용법은 이런 식입니다:

    >>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
    We are the knights who say "Ni!"

중괄호와 그 안에 있는 문자들 (포맷 필드라고 부른다) 은 `str.format()` 메서드로 전달된 객체들로 치환됩니다. 중괄호 안의 숫자는 `str.format()` 메서드로 전달된 객체들의 위치를 가리키는데 사용될 수 있습니다.

    >>> print('{0} and {1}'.format('spam', 'eggs'))
    spam and eggs
    >>> print('{1} and {0}'.format('spam', 'eggs'))
    eggs and spam

`str.format()` 메서드에 키워드 인자가 사용되면, 그 값들은 인자의 이름을 사용해서 지정할 수 있습니다.

    >>> print('This {food} is {adjective}.'.format(
    ...       food='spam', adjective='absolutely horrible'))
    This spam is absolutely horrible.

위치와 키워드 인자를 자유롭게 조합할 수 있습니다:

    >>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                           other='Georg'))
    The story of Bill, Manfred, and Georg.

`'!a'` (`ascii()` 를 적용한다), `'!s'` (`str()` 을 적용합니다), `'!r'` (`repr()` 을 적용한다) 은 포맷 전에 값을 변환하는 데 사용됩니다:

    >>> contents = 'eels'
    >>> print('My hovercraft is full of {}.'.format(contents))
    My hovercraft is full of eels.
    >>> print('My hovercraft is full of {!r}.'.format(contents))
    My hovercraft is full of 'eels'.

선택적인 `':'` 과 포맷 지정자가 필드 이름 뒤에 올 수 있습니다. 이것으로 값이 포맷되는 방식을 더 정교하게 제어할 수 있습니다. 다음 예는 원주율을 소수점 이하 세 자리로 반올림합니다.

    >>> import math
    >>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
    The value of PI is approximately 3.142.

`':'` 뒤에 정수를 전달하면 해당 필드의 최소 문자 폭이 됩니다. 표를 예쁘게 만들 때 편리합니다.

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
    >>> for name, phone in table.items():
    ...     print('{0:10} ==> {1:10d}'.format(name, phone))
    ...
    Jack       ==>       4098
    Dcab       ==>       7678
    Sjoerd     ==>       4127

나누고 싶지 않은 정말 긴 포맷 문자열이 있을 때, 포맷할 변수들을 위치 대신에 이름으로 지정할 수 있다면 좋을 것입니다. 간단히 딕셔너리를 넘기고 키를 액세스하는데 꺾쇠괄호 `'[]'` 를 사용하면 됩니다

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
    ...       'Dcab: {0[Dcab]:d}'.format(table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678

〈**〉 표기법을 사용해서 table을 키워드 인자로 전달해도 같은 결과를 얻을 수 있습니다.

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678

이 방법은 모든 지역 변수들을 담은 딕셔너리를 돌려주는 내장 함수 `vars()` 와 함께 사용할 때 특히 쓸모가 있습니다.

`str.format()` 를 사용한 문자열 포매팅의 완전한 개요는 Format String Syntax 을 보세요.

### 7.1.1. 예전의 문자열 포매팅

`%` 연산자도 문자열 포매팅에 사용될 수 있습니다. 왼쪽 인자를 오른쪽 인자에 적용되는 `sprintf()`-스타일 포맷 문자열로 해석하고, 이 포매팅 연산의 결과로 얻어지는 문자열을 돌려줍니다. 예를 들어:

    >>> import math
    >>> print('The value of PI is approximately %5.3f.' % math.pi)
    The value of PI is approximately 3.142.

더 자세한 내용은 printf 스타일 문자열 포매팅 섹션에 나옵니다.

## 7.2. 파일을 읽고 쓰기

`open()` 은 파일 객체 를 돌려주고, 두 개의 인자를 주는 방식이 가장 많이 사용됩니다: `open(filename, mode)`.

    >>> f = open('workfile', 'w')

첫 번째 인자는 파일 이름을 담은 문자열입니다. 두 번째 인자는 파일이 사용될 방식을 설명하는 몇 개의 문자들을 담은 또 하나의 문자열입니다. mode 는 파일을 읽기만 하면 `'r'`, 쓰기만 하면 `'w'` (같은 이름의 이미 존재하는 파일은 삭제됩니다) 가 되고, `'a'` 는 파일을 덧붙이기 위해 엽니다; 파일에 기록되는 모든 데이터는 자동으로 끝에 붙습니다. `'r+'` 는 파일을 읽고 쓰기 위해 엽니다. mode 인자는 선택적인데, 생략하면 `'r'` 이 가정됩니다.

보통, 파일은 텍스트 모드 (text mode) 로 열리는데, 이 뜻은, 파일에 문자열을 읽고 쓰고, 파일에는 특정한 인코딩으로 저장된다는 것입니다. 인코딩이 지정되지 않으면 기본값은 플랫폼 의존적입니다 (`open()` 을 보세요). mode 에 덧붙여진 `'b'` 는 파일을 바이너리 모드 (binary mode) 로 엽니다: 이제 데이터는 바이트열 객체의 형태로 읽고 쓰입니다. 텍스트를 포함하지 않는 모든 파일에는 이 모드를 사용해야 합니다.

텍스트 모드에서, 읽을 때의 기본 동작은 플랫폼 의존적인 줄 종료 (유닉스에서 `\n`, 윈도우에서 `\r\n`) 를 단지 `\n` 로 변경하는 것입니다. 텍스트 모드로 쓸 때, 기본 동작은 `\n` 를 다시 플랫폼 의존적인 줄 종료로 변환하는 것입니다. 이 파일 데이터에 대한 무대 뒤의 수정은 텍스트 파일의 경우는 문제가 안 되지만, `JPEG` 이나 `EXE` 파일과 같은 바이너리 데이터를 망치게 됩니다. 그런 파일을 읽고 쓸 때 바이너리 모드를 사용하도록 주의하세요.

파일 객체를 다룰 때 `with` 키워드를 사용하는 것은 좋은 습관입니다. 혜택은 도중 예외가 발생하더라도 스위트가 종료될 때 파일이 올바르게 닫힌다는 것입니다. `with` 를 사용하는 것은 동등한 `try`-`finally` 블록을 쓰는 것에 비교해 훨씬 짧기도 합니다:

    >>> with open('workfile') as f:
    ...     read_data = f.read()
    >>> f.closed
    True

`with` 키워드를 사용하지 않으면, `f.close()` 를 호출해서 파일을 닫고 사용된 시스템 자원을 즉시 반납해야 합니다. 명시적으로 파일을 닫지 않으면, 파이썬의 가비지 수거기가 결국에는 객체를 파괴하고 여러분을 대신해서 파일을 닫게 되지만, 파일이 한동안 열린 상태로 남아있게 됩니다. 또 다른 위험은 다른 파이썬 구현들은 이 뒷정리를 서로 다른 시점에 수행한다는 것입니다.

파일 객체가 닫힌 후에는, `with` 문이나 `f.close()` 를 호출하는 경우 모두, 파일 객체를 사용하려는 시도는 자동으로 실패합니다.

    >>> f.close()
    >>> f.read()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: I/O operation on closed file.

### 7.2.1. 파일 객체의 매소드

이 섹션의 나머지 예들은 `f` 라는 파일 객체가 이미 만들어졌다고 가정합니다.

파일의 내용을 읽으려면, `f.read(size)` 를 호출하는데, 일정량의 데이터를 읽고 문자열 (텍스트 모드 에서) 이나 바이트열 (바이너리 모드에서) 로 돌려줍니다. size 는 선택적인 숫자 인자다. size 가 생략되거나 음수면 파일의 내용 전체를 읽어서 돌려줍니다; 파일의 크기가 기계의 메모리보다 두 배 크다면 여러분이 감당할 문제입니다. 그렇지 않으면 최대 size 바이트를 읽고 돌려줍니다. 파일의 끝에 도달하면, `f.read()` 는 빈 문자열 (`''`) 을 돌려줍니다.

    >>> f.read()
    'This is the entire file.\n'
    >>> f.read()
    ''

`f.readline()` 은 파일에서 한 줄을 읽습니다; 개행 문자 (`\n`) 는 문자열의 끝에 보존되고, 파일이 개행문자로 끝나지 않는 때에만 파일의 마지막 줄에서만 생략됩니다. 이렇게 반환 값을 모호하지 않게 만듭니다; `f.readline()` 가 빈 문자열을 돌려주면, 파일의 끝에 도달한 것이지만, 빈 줄은 `'\n'`, 즉 하나의 개행문자만을 포함하는 문자열로 표현됩니다.

    >>> f.readline()
    'This is the first line of the file.\n'
    >>> f.readline()
    'Second line of the file\n'
    >>> f.readline()
    ''

파일에서 줄들을 읽으려면, 파일 객체에 대해 루핑할 수 있습니다. 이것은 메모리 효율적이고, 빠르며 간단한 코드로 이어집니다:

    >>> for line in f:
    ...     print(line, end='')
    ...
    This is the first line of the file.
    Second line of the file

파일의 모든 줄을 리스트로 읽어 들이려면 `list(f)` 나 `f.readlines()` 를 쓸 수 있습니다.

`f.write(string)` 은 string 의 내용을 파일에 쓰고, 출력된 문자들의 개수를 돌려줍니다.

    >>> f.write('This is a test\n')
    15

다른 형의 객체들은 쓰기 전에 변환될 필요가 있습니다 – 문자열 (텍스트 모드에서) 이나 바이트열 객체 (바이너리 모드에서) 로 –:

    >>> value = ('the answer', 42)
    >>> s = str(value)  # convert the tuple to string
    >>> f.write(s)
    18

`f.tell()` 은 파일의 현재 위치를 가리키는 정수를 돌려주는데, 바이너리 모드의 경우 파일의 처음부터의 바이트 수로 표현되고 텍스트 모드의 경우는 불투명한 숫자입니다.

파일 객체의 위치를 바꾸려면, `f.seek(offset, from_what)` 를 사용합니다. 위치는 기준점에 offset 을 더해서 계산됩니다; 기준점은 from_what 인자로 선택합니다. from_what 값이 0이면 파일의 처음부터 측정하고, 1이면 현재 파일 위치를 사용하고, 2 는 파일의 끝을 기준점으로 사용합니다. from_what 은 생략될 수 있고, 기본값은 0이라서 파일의 처음을 기준점으로 사용합니다.

    >>> f = open('workfile', 'rb+')
    >>> f.write(b'0123456789abcdef')
    16
    >>> f.seek(5)      # Go to the 6th byte in the file
    5
    >>> f.read(1)
    b'5'
    >>> f.seek(-3, 2)  # Go to the 3rd byte before the end
    13
    >>> f.read(1)
    b'd'

텍스트 파일에서는 (모드 문자열에 `b` 가 없이 열린 것들), 파일의 시작에 상대적인 위치 변경만 허락되고 (예외는 `seek(0, 2)` 를 사용해서 파일의 끝으로 위치를 변경하는 경우입니다), 올바른 offset 값은 `f.tell()` 이 돌려준 값과 0뿐입니다. 그 밖의 다른 offset 값은 정의되지 않은 결과를 낳습니다.

파일 객체는 `isatty()` 나 `truncate()` 같은 몇 가지 메서드를 더 갖고 있는데, 덜 자주 사용됩니다; 파일 객체에 대한 완전한 안내는 라이브러리 레퍼런스를 참조하세요.

### 7.2.2. `json` 으로 구조적인 데이터를 저장하기

문자열은 파일에 쉽게 읽고 쓸 수 있습니다. 숫자는 약간의 수고를 해야 하는데, `read()` 메서드가 문자열만을 돌려주기 때문입니다. 이 문자열을 `int()` 같은 함수로 전달해야만 하는데, `'123'` 같은 문자열을 받고 숫자 값 123을 돌려줍니다. 중첩된 리스트나 딕셔너리 같은 더 복잡한 데이터를 저장하려고 할 때, 수작업으로 파싱하고 직렬화하는 것이 까다로울 수 있습니다.

사용자가 반복적으로 복잡한 데이터형을 파일에 저장하는 코드를 작성하고 디버깅하도록 하는 대신, 파이썬은 JSON (JavaScript Object Notation) 이라는 널리 쓰이는 데이터 교환 형식을 사용할 수 있게 합니다. `json` 이라는 표준 모듈은 파이썬 데이터 계층을 받아서 문자열 표현으로 바꿔줍니다; 이 절차를 직렬화 (serializing) 라고 부릅니다. 문자열 표현으로부터 데이터를 재구성하는 것은 역 직렬화 (deserializing) 라고 부릅니다. 직렬화와 역 직렬화 사이에서, 객체를 표현하는 문자열은 파일이나 데이터에 저장되거나 네트워크 연결을 통해 원격 기계로 전송될 수 있습니다.

참고

JSON 형식은 데이터 교환을 위해 현대 응용 프로그램들이 자주 사용합니다. 많은 프로그래머가 이미 이것에 익숙하므로, 연동성을 위한 좋은 선택이 됩니다.

객체 `x` 가 있을 때, 간단한 한 줄의 코드로 그것의 JSON 문자열 표현을 볼 수 있습니다:

    >>> import json
    >>> json.dumps([1, 'simple', 'list'])
    '[1, "simple", "list"]'

`dump()` 라는 `dumps()` 함수의 변종은 객체를 텍스트 파일 로 직렬화합니다. 그래서 `f` 가 쓰기를 위해 열린 텍스트 파일 이면, 이렇게 할 수 있습니다:

    json.dump(x, f)

객체를 다시 디코드하려면, `f` 가 읽기를 위해 열린 텍스트 파일 객체일 때:

    x = json.load(f)

이 간단한 직렬화 테크닉이 리스트와 딕셔너리를 다룰 수 있지만, 임의의 클래스 인스턴스를 JSON 으로 직렬화하기 위해서는 약간의 수고가 더 필요합니다. `json` 모듈의 레퍼런스는 이 방법에 대한 설명을 담고 있습니다.

더 보기

`pickle` - 피클 모듈

JSON 에 반해, pickle 은 임의의 복잡한 파이썬 객체들을 직렬화할 수 있는 프로토콜입니다. 파이썬에 국한되고 다른 언어로 작성된 응용 프로그램들과 통신하는데 사용될 수 없습니다. 기본적으로 안전하지 않기도 합니다: 믿을 수 없는 소스에서 온 데이터를 역 직렬화할 때, 숙련된 공격자에 의해 데이터가 조작되었다면 임의의 코드가 실행될 수 있습니다.