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 とも呼びます) では、文字列の頭に fF を付け、式を {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() returns a file object, and is most commonly used with two positional arguments and one keyword argument: open(filename, mode, encoding=None)

>>> f = open('workfile', 'w', encoding="utf-8")

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

Normally, files are opened in text mode, that means, you read and write strings from and to the file, which are encoded in a specific encoding. If encoding is not specified, the default is platform dependent (see open()). Because UTF-8 is the modern de-facto standard, encoding="utf-8" is recommended unless you know that you need to use a different encoding. Appending a 'b' to the mode opens the file in binary mode. Binary mode data is read and written as bytes objects. You can not specify encoding when opening file in binary mode.

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

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

>>> with open('workfile', encoding="utf-8") 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
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'

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

json.dump(x, f)

To decode the object again, if f is a binary file or text file object which has been opened for reading:

x = json.load(f)

注釈

JSON files must be encoded in UTF-8. Use encoding="utf-8" when opening JSON file as a text file for both of reading and writing.

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

参考

pickle - pickle モジュール

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