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