7. 入力と出力
*************

プログラムの出力方法にはいくつかの種類があります。 データを人間が読め
る形で出力することもあれば、将来使うためにファイルに書くこともあります
。 この章では、こうした幾つかの出力の方法について話します。


7.1. 出力を見やすくフォーマットする
===================================

これまでに、値を出力する二つの方法: *式文 (expression statement)* と
"print()" 関数が出てきました。 (第三はファイルオブジェクトの "write()"
メソッドを使う方法です。標準出力を表すファイルは "sys.stdout" で参照で
きます。詳細はライブラリリファレンスを参照してください。)

単に空白区切りで値を並べただけの出力よりも、フォーマットを制御したいと
思うことはよくあることでしょう。 出力をフォーマットする方法はいくつか
あります。

* フォーマット済み文字列リテラル を使うには、開き引用符や三重の開き引
  用符の前に "f" あるいは "F" を付けて文字列を始めます。 この文字列の
  内側では、文字 "{" と文字 "}" の間に Python の式が書け、その式から変
  数やリテラル値が参照できます。

     >>> year = 2016
     >>> event = 'Referendum'
     >>> f'Results of the {year} {event}'
     'Results of the 2016 Referendum'

* 文字列の "str.format()" メソッドは、もう少し手間がかかります。 ここ
  でも "{" と "}" を使って変数に代入する場所の印を付けて、細かいフォー
  マットの指示を出せますが、フォーマットされる対象の情報を与える必要が
  あります。

     >>> yes_votes = 42_572_654
     >>> no_votes = 43_132_495
     >>> percentage = yes_votes / (yes_votes + no_votes)
     >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
     ' 42572654 YES votes  49.67%'

* 最後に、文字列のスライス操作や結合操作を使い、全ての文字列を自分で処
  理し、思い通りのレイアウトを作成できます。 文字列型には、文字列の間
  隔を調整して指定されたカラム幅に揃えるのに便利な操作を行うメソッドが
  いくつかあります。

凝った出力である必要は無いけれど、デバッグ目的で変数をすばやく表示した
いときは、 "repr()" 関数か "str()" 関数でどんな値も文字列に変換できま
す。

"str()" 関数は値の人間に読める表現を返すためのもので、 "repr()" 関数は
インタープリタに読める (あるいは同値となる構文がない場合は必ず
"SyntaxError" になるような) 表現を返すためのものです。人間が読むのに適
した特定の表現を持たないオブジェクトにおいては、 "str()" は "repr()"
と同じ値を返します。数値や、リストや辞書を始めとするデータ構造など、多
くの値がどちらの関数に対しても同じ表現を返します。一方、文字列は、2つ
の異なる表現を持っています。

例:

   >>> 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'))"

"string" モジュールの "Template" クラスも、文字列中の値を置換する別の
方法を提供しています。 "$x" のようなプレースホルダーを使い、その箇所と
辞書にある値を置き換えますが、使えるフォーマット方式はとても少ないです
。


7.1.1. フォーマット済み文字列リテラル
-------------------------------------

フォーマット済み文字リテラル (短くして f-string とも呼びます) では、文
字列の頭に "f" か "F" を付け、式を "{expression}" と書くことで、
Python の式の値を文字列の中に入れ込めます。

オプションのフォーマット指定子を式の後ろに付けられます。 このフォーマ
ット指定子によって値のフォーマット方式を制御できます。 次の例では、円
周率πを小数点以下3桁に丸めてフォーマットしています:

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

"':'" の後ろに整数をつけると、そのフィールドの最小の文字幅を指定できま
す。 この機能は縦を揃えるのに便利です。

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

他の修飾子は、フォーマットする前に値を変換するのに使えます。 "'!a'" は
"ascii()" を、 "'!s'" は "str()" を、 "'!r'" は "repr()" を適用します:

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

これらのフォーマット仕様の参考資料として、 書式指定ミニ言語仕様 のガイ
ドを参照してください。


7.1.2. 文字列の format() メソッド
---------------------------------

"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.

もしも長い書式文字列があり、それを分割したくない場合には、変数を引数の
位置ではなく変数の名前で参照できるとよいでしょう。これは、辞書を引数に
渡して、角括弧 "'[]'" を使って辞書のキーを参照することで可能です。

   >>> 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()" と組み合わ
せると特に便利です。

例として、下のコード行は与えられた整数とその 2 乗と 3 乗がきちんと揃っ
た列を生成します:

   >>> 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

"str.format()" による文字列書式設定の完全な解説は、 書式指定文字列の文
法 を参照してください。


7.1.3. 文字列の手作業でのフォーマット
-------------------------------------

次は 2 乗と 3 乗の値からなる同じ表を手作業でフォーマットしたものです:

   >>> 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

(各カラムの間のスペース一個分は "print()" の動作で追加されていることに
注意してください。 "print()" は常に引数間に空白を追加します。)

文字列オブジェクトの "str.rjust()" メソッドは、指定された幅のフィール
ド内に文字列が右寄せで入るように左側に空白を追加します。 同様のメソッ
ドとして、 "str.ljust()" と "str.center()" があります。 これらのメソッ
ドは何か出力を行うわけではなく、ただ新しい文字列を返します。 入力文字
列が長すぎる場合、文字列を切り詰めることはせず、値をそのまま返します。
この仕様のためにカラムのレイアウトが滅茶苦茶になるかもしれませんが、嘘
の値が代わりに書き出されるよりはましです。 (本当に切り詰めを行いたいの
なら、全てのカラムに "x.ljust(n)[:n]" のようにスライス表記を加えること
もできます。)

もう一つのメソッド、 "str.zfill()" は、数値文字列の左側をゼロ詰めしま
す。このメソッドは正と負の符号を正しく扱います:

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


7.1.4. 古い文字列書式設定方法
-----------------------------

% 演算子 (剰余) は文字列のフォーマットでも使えます。 "'string' %
values" という文字列が与えられた場合、"string" の中の "%" 部分はゼロあ
るいは "values" の余りの要素に置換えられます。 この操作は文字列補間と
して知られています。 例えば:

   >>> 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()" は *file object* を返します。大抵、 "open(filename, mode)" の
ように二つの引数を伴って呼び出されます。

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

最初の引数はファイル名の入った文字列です。二つめの引数も文字列で、ファ
イルをどのように使うかを示す数個の文字が入っています。 *mode* は、ファ
イルが読み出し専用なら "'r'" 、書き込み専用 (同名の既存のファイルがあ
れば消去されます) なら "'w'" とします。 "'a'" はファイルを追記用に開き
ます。ファイルに書き込まれた内容は自動的にファイルの終端に追加されます
。 "'r+'" はファイルを読み書き両用に開きます。 *mode* 引数は省略可能で
、省略された場合には "'r'" であると仮定します。

通常、ファイルはテキストモード (*text mode*) で開かれ、特定のエンコー
ディングでエンコードされたファイルに対して文字列を読み書きします。エン
コーディングが指定されなければ、デフォルトはプラットフォーム依存です
("open()" を参照してください) 。モードに "'b'" をつけるとファイルをバ
イナリモード (*binary mode*) で開き、 byte オブジェクトを読み書きしま
す。テキストファイル以外のすべてのファイルはバイナリモードを利用するべ
きです。

テキストモードの読み取りでは、プラットフォーム固有の行末記号 (Unix で
は "\n" 、Windows では "\r\n") をただの "\n" に変換するのがデフォルト
の動作です。テキストモードの書き込みでは、 "\n" が出てくる箇所をプラッ
トフォーム固有の行末記号に戻すのがデフォルトの動作です。この裏で行われ
るファイルデータの変換はテキストファイルには上手く働きますが、 "JPEG"
ファイルや "EXE" ファイルのようなバイナリデータを破壊する恐れがありま
す。そのようなファイルを読み書きする場合には注意して、バイナリモードを
使うようにしてください。

ファイルオブジェクトを扱うときに "with" キーワードを使うのは良い習慣で
す。 その利点は、処理中に例外が発生しても必ず最後にファイルをちゃんと
閉じることです。 "with" を使うと、同じことを "try"-"finally" ブロック
を使って書くよりずっと簡潔に書けます:

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

   >>> # We can check that the file has been automatically closed.
   >>> f.closed
   True

もし "with" キーワードを使用しない場合は、ファイルを閉じ、このファイル
のために利用されたシステムのリソースを直ちに解放するために "f.close()"
を呼び出してください。

警告:

  "f.write()" を "with" キーワードや "f.close()" を使わずに呼び出した
  場合、プログラムが正常に終了した場合でも、 "f.write()" の実引数がデ
  ィスクに完全に **書き込まれないことがあります** 。

"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)" を呼び出します。このメソ
ッドはある量のデータを読み出して、文字列 (テキストモードの場合) か
bytes オブジェクト (バイナリーモードの場合) として返します。 *size* は
オプションの数値引数です。 *size* が省略されたり負の数であった場合、フ
ァイルの内容全てを読み出して返します。ただし、ファイルがマシンのメモリ
の二倍の大きさもある場合にはどうなるかわかりません。 *size* が負でない
数ならば、最大で (テキストモードの場合) *size* 文字、(バイナリモードの
場合) *size* バイトを読み出して返します。ファイルの終端にすでに達して
いた場合、 "f.read()" は空の文字列 ("''") を返します。

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

"f.readline()" はファイルから 1 行だけを読み取ります。改行文字 ("\n")
は読み出された文字列の終端に残ります。改行が省略されるのは、ファイルが
改行で終わっていない場合の最終行のみです。これは、戻り値があいまいでな
いようにするためです; "f.readline()" が空の文字列を返したら、ファイル
の終端に達したことが分かります。一方、空行は "'\n'"、すなわち改行 1 文
字だけからなる文字列で表現されます。

   >>> 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

オブジェクトの他の型は、書き込む前に変換しなければなりません -- 文字列
(テキストモード) と bytes オブジェクト (バイナリーモード) のいずれかで
す:

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

"f.tell()" は、ファイルオブジェクトのファイル中における現在の位置を示
す整数を返します。ファイル中の現在の位置は、バイナリモードではファイル
の先頭からのバイト数で、テキストモードでは不明瞭な値で表されます。

ファイルオブジェクトの位置を変更するには、"f.seek(offset, whence)" を
使います。ファイル位置は基準点 (reference point) にオフセット値
*offset* を足して計算されます。参照点は *whence* 引数で選びます。
*whence* の値が 0 ならばファイルの 先頭から測り、1 ならば現在のファイ
ル位置を使い、2 ならばファイルの終端を参照点として使います。*whence*
は省略することができ、デフォルトの値は 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'

テキストファイル (mode 文字列に "b" を付けなかった場合) では、ファイル
の先頭からの相対位置に対するシークだけが許可されています (例外として、
"seek(0, 2)" でファイルの末尾へのシークは可能です)。また、唯一の有効な
*offset* 値は "f.tell()" から返された値か、0 のいずれかです。それ以外
の *offset* 値は未定義の振る舞いを引き起こします。

ファイルオブジェクトには、他にも "isatty()" や "truncate()" といった、
あまり使われないメソッドがあります。ファイルオブジェクトについての完全
なガイドは、ライブラリリファレンスを参照してください。


7.2.2. "json" による構造化されたデータの保存
--------------------------------------------

文字列は簡単にファイルに書き込んだり、ファイルから読み込んだりすること
ができます。数値の場合には少し努力が必要です。というのも、"read()" メ
ソッドは文字列しか返さないため、"int()" のような関数にその文字列を渡し
て、たとえば文字列 "'123'" のような文字列を、数値 123 に変換しなくては
ならないからです。もっと複雑なデータ型、例えば入れ子になったリストや辞
書の場合、手作業でのパースやシリアライズは困難になります。

ユーザが毎回コードを書いたりデバッグしたりして複雑なデータ型をファイル
に保存するかわりに、Python では一般的なデータ交換形式である JSON
(JavaScript Object Notation) を使うことができます。この標準モジュール
"json" は、Python のデータ 階層を取り、文字列表現に変換します。この処
理は *シリアライズ (serializing)* と呼ばれます。文字列表現からデータを
再構築することは、*デシリアライズ (deserializing)* と呼ばれます。シリ
アライズされてからデシリアライズされるまでの間に、オブジェクトの文字列
表現はファイルやデータの形で保存したり、ネットワークを通じて離れたマシ
ンに送ったりすることができます。

注釈:

  JSON 形式は現代的なアプリケーションでデータをやりとりする際によく使
  われます。多くのプログラマーが既に JSON に詳しいため、JSON はデータ
  の相互交換をする場合の良い選択肢です。

オブジェクト "x" があり、その JSON 形式の文字列表現を見るには、単純な1
行のコードを書くだけです:

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

"dumps()" に似た関数に、"dump()" があり、こちらは単純にオブジェクトを
*text file* にシリアライズします。"f" が書き込み用に開かれた *text
file* だとすると、次のように書くことができます:

   json.dump(x, f)

逆にデシリアライズするには、"f" が読み込み用に開かれた *text file* だ
とすると、次のようになります:

   x = json.load(f)

このような単純なシリアライズをする手法は、リストや辞書を扱うことはでき
ますが、任意のクラス・インスタンスを JSON にシリアライズするにはもう少
し努力しなくてはなりません。"json" モジュールのリファレンスにこれにつ
いての解説があります。

参考:

  "pickle" - pickle モジュール

  JSON とは対照的に、 *pickle* は任意の複雑な Python オブジェクトをシ
  リアライズ可能なプロトコルです。しかし、Python に特有のプロトコルで
  、他の言語で記述されたアプリケーションと通信するのには使えません。さ
  らに、デフォルトでは安全でなく、信頼できない送信元から送られてきた、
  スキルのある攻撃者によって生成された pickle データをデシリアライズす
  ると、攻撃者により任意のコードが実行されてしまいます。
