朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

Python の open 関数と io モジュールをきちんと使うために

はじめに

開発部の ikasat です。 Python の言語・ライブラリ・処理系はプログラマのタスクを手早く簡単にこなせるようにするために設計されており、数行程度のコードを書いただけでも内部で様々なことをやってくれます。 しかし、この便利さが特定のユースケースにおいては逆にお節介になってしまうこともあり、また内部動作が複雑であることにより挙動を修正する方法も分からなくなりがちです。

特に組み込みの open 関数や標準入出力 (sys.stdin, sys.stdout) はその最たる例であり、UnicodeEncodeError / UnicodeDecodeErrorTypeError: a bytes-like object is required は Python を使った人であれば誰もが見たことのあるエラーメッセージでしょう。 私自身これまでこの類のエラーが出た時には検索して出てきた情報を基に場当たり的な対処をしてきましたが、この機会に入出力の仕組みと io モジュールについて調べることでようやく問題に確実に対処できるようになりました。 というわけで今回は Python の入出力についてやや深めに解説していきます。

対象読者

本記事の構成

  • 最初に I/O に関するよくある問題への対処方法について記載します
  • 次にこの対処方法に関連した io モジュールの解説を行います
  • おまけとして CPython の内部実装に触れたり他言語 (Java, Go) の I/O 系モジュールの比較を行ったりします

対象環境

  • Windows, macOS, Linux の Python 3.6 以降を対象とします
  • メインの動作確認環境は Linux の Python 3.9.5 です
  • 細かい挙動については CPython 3.9.5 のソースコードを確認しています

よくある問題への対処

この節では Python の入出力周りで発生しがちな問題の解決策について提示します。

openencoding 引数を指定しよう

open 関数でテキストファイルを読み書きする際にはとにかく encoding 引数を指定しましょう。 基本的にこれが UnicodeEncodeError / UnicodeDecodeError に対する最も有効な解決策となります。

with open("foo.txt", encoding="utf-8") as f:
    obj = json.load(f)

encoding を指定しなかった場合はデフォルトで locale.getpreferredencoding() で取得可能なエンコーディングが利用されますが、これにより予期しない事態が発生する場合があります。 特に Windows の日本語ロケールではほぼ強制的に "cp932" (Shift_JIS の拡張)となるため UnicodeDecodeError / UnicodeEncodeError が発生しやすくなります。

一括してファイル入出力のエンコーディングを変更するには?

自分ですぐに修正できるソースコードなら先ほどのように一つ一つ encoding 引数を指定すれば解決するのですが、例えば pip install で外部パッケージをインストールしている最中にエラーになった場合などはこの方法は使えません。 よって、以下の一括してファイル入出力のエンコーディングを変更する方法を利用することになります。

  • Python 3.7 以降であり読んでいるファイルが UTF-8 であれば UTF-8 モードを使う
    • PYTHONUTF8=1 環境変数を利用する
    • 現代において pip で導入するようなパッケージが読み書きするファイルは UTF-8 が前提のはずなので大抵はこれで事足りる
  • Linux の場合ロケール系の環境変数を指定する
    • 例えば UTF-8 の場合 LANGLC_ALLja_JP.UTF-8, en_US.UTF-8, C.UTF-8 等を指定する
  • usercustomize 等を使い open 関数を差し替えて強制的に encoding 引数を変更する
    • かなり無理矢理だがどの環境・バージョン・エンコーディングでも利用できる(詳細な手順については割愛)

Python 3.7 以降は PYTHONUTF8 環境変数に空でない文字列を指定する、もしくは処理系の起動時コマンドライン引数として -Xutf8 を与えることで UTF-8 モードになります。 環境変数を(一時的に)設定して起動する例について以下に示します。

PYTHONUTF8=1 python3 foo.py

Windows の場合は以下のように起動します。

set PYTHONUTF8=1
py foo.py

str / bytes の変換に使う encoding について

open 関数の encoding 引数とは異なり、strencode メソッドや bytesdecode メソッドの encoding 引数を指定しない場合のデフォルト値は "utf-8" です。 実際には sys.getdefaultencoding() の結果が使われますがこれは Python 3 では常に "utf-8" です。

>>> 'あ'.encode()
b'\xe3\x81\x82'
>>> b'\xe3\x81\x82'.decode()
'あ'
>>> sys.getdefaultencoding()
'utf-8'

これとは別に組み込みの str / bytes 関数を使って文字列とバイト列を変換する方法もありますが、こちらの場合はきちんと encoding 引数を指定しないとエラーが発生したり意図しない文字列が返ってきたりすることに注意しましょう。そのため str / bytes の変換目的では上記の encode / decode メソッドを一貫して用いた方がよいと思われます。

>>> bytes('あ', encoding='utf-8')
b'\xe3\x81\x82'
>>> str(b'\xe3\x81\x82', encoding='utf-8')
'あ'
>>> bytes('あ')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: string argument without an encoding
>>> str(b'\xe3\x81\x82')
"b'\\xe3\\x81\\x82'"

標準入出力のエンコーディングを変えるには?

まず本当に標準入出力のエンコーディングを変更すべきか、変更して問題が解決するのかをあらかじめよく考えましょう。 その上で標準入出力のエンコーディングを強制的に変更するには以下の方法が利用できます。

Windows 以外の場合 PYTHONIOENCODING を書き換えることで標準入出力の encoding を指定できます。

PYTHONIOENCODING=cp932 python3 foo.py

Windows の場合コンソール入出力に常に Unicode 系の文字コードを利用する(正確に言うとワイド文字 API を利用する)ためこの変数の指定は意味を持ちません。 これを回避するには PYTHONLEGACYWINDOWSSTDIO 環境変数を指定します。

set PYTHONIOENCODING=cp932
set PYTHONLEGACYWINDOWSSTDIO=1
py foo.py

Python 3.7 以降であれば reconfigure メソッドを呼ぶことでもエンコーディング等の設定を変更できます。 この際 stdin についてはまだ 1 バイトも入力を消費していない必要があります。

sys.stdin.reconfigure(encoding="cp932")
sys.stdout.reconfigure(encoding="cp932")
sys.stderr.reconfigure(encoding="cp932")

Python 3.6 以前であれば sys.stdin / sys.stdout / sys.stderr 自体を以下のように置き換えることで個別に設定できます(詳細は後述しますが line_buffering の指定は元の挙動に寄せています)。

sys.stdin = io.TextIOWrapper(
    sys.stdin.buffer,
    encoding="cp932",
    line_buffering=sys.stdin.isatty(),
)
sys.stdout = io.TextIOWrapper(
    sys.stdout.buffer,
    encoding="cp932",
    line_buffering=sys.stdout.isatty(),
)
sys.stderr = io.TextIOWrapper(
    sys.stderr.buffer,
    encoding="cp932",
    line_buffering=True,
)

codecs.open ではなく組み込みの open を使おう

codecs モジュールの open ではなく組み込みの open を使いましょう。 Python 3 系では組み込みの open 関数は io モジュールの open と同義なのですが、 Python 2.0 からある codecs モジュールと比べて 2.6 で導入された io モジュールの方が新しい設計となっています。 Web 検索では Python 2 時代の文書がまだ多くヒットするため codecs.open もよく見かけるのですが、あるファイルをテキストとして読み書きする通常のケースでは組み込みの open 関数で十分です。

# Before
with codecs.open("foo.txt", encoding="cp932") as f:
    pass
# After
with open("foo.txt", encoding="cp932") as f:
    pass

codecsStreamReaderStreamWriter を利用してストリーム処理を行うケースの代わりとしては io.TextIOWrapper が利用できます。

# Before
text_in = codecs.getreader("cp932")(binary_in)
text_out = codecs.getwriter("cp932")(binary_out)
# After
text_in = io.TextIOWrapper(binary_in, encoding="cp932")
text_out = io.TextIOWrapper(binary_out, encoding="cp932", write_through=True)

なお StreamReader / StreamWriter の問題点については以下の PEP に記載されています(PEP 自体の Status は Deferred ですが……)。

ファイルへの書き込みを正しく行おう

ファイルオブジェクトの書き込みについては以下の条件を満たすまで実際にはファイルへの書き込みは行われません。

  • 明示的に flush() メソッドを呼んだ場合
  • ファイルオブジェクト内部のバッファが埋まった場合
  • (テキストモードかつラインバッファリングが有効な場合)改行文字が存在した場合
  • close() メソッドが呼ばれた場合
    • __del__, __exit__ メソッドが呼ばれた場合も同様

flush()close() の呼び忘れが起こると書き込みが中途半端になるなどの意図しない結果を招く場合があります。 できる限り with ブロックでファイルオブジェクトの生存期間を明示するようにしておくとこのような問題は起こりません。 この場合は with ブロックの終わりに __exit__ メソッドが呼ばれることによって書き込みが行われます。

# Before
f = open("foo.txt", encoding="utf-8")
...
f.close()
# After
with open("foo.txt", encoding="utf-8") as f:
    ...

Python の I/O を知る

この節では Python の open 関数の詳しい挙動とそれを支える io モジュールのクラスについて解説します。

Python における strbytes の変換

Python の I/O を理解するにあたって、そもそも Python 3 の str とは何か、ということを知っておく必要があります。

まず Python において「文字」は「Unicode コードポイント(符号位置)」で表されます。 例えば文字 には Unicode において 12354 (16進数で 0x3042)というコードポイントが割り当てられています。文字のコードポイントは組み込みの ord 関数で調べることができます。

>>> ord("あ")
12354
>>> ord("い")
12356
>>> ord("う")
12358

そして str は「Unicode コードポイントの列」です。 簡単に言うと文字列 "あいう" は内部的には [12354, 12356, 12358] のような数値の列として扱われていると考えてください(正確には Python のリストを使っているわけではありませんし、利用される文字種によってより効率の良い内部表現が使われています)。

一方で Python の外部から入力されてくるもの・外部に出力するものは全て bytes となっており、これはエンコーディングもそもそもテキストかどうかも不明な単なるバイト列(バイナリ)です。 この入出力を str (テキスト)として扱うには必ずエンコーディングを与えて Unicode のコードポイントに変換してあげる必要があります。例えば bytesdecode メソッドや strencode メソッドは以下のような意味合いを持ちます。

  • bytesdecode(encoding=...) メソッド
    • 単なるバイト列 (bytes) を指定された encoding の文字列であると解釈(デコード)し、 Unicode のコードポイント列 (str) に変換する
  • strencode(encoding=...) メソッド
    • Unicode のコードポイント列 (str) を指定された文字コードの文字列に変換(エンコード)し、それを単なるバイト列 (bytes) として扱う

なお、ここでわざわざこのような説明をしたのはプログラミング言語によっては外部入出力をそのまま文字列のように扱う場合があるためです(Python 2 時代の str もそうでした)。 とにかく Python 3 の外部入出力をテキストとして扱うには Unicode コードポイントとの相互変換が必要であり、これは後述の「raw I/O・バイナリ I/O」と「テキスト I/O」の区別に関わってきます。

io モジュールについて知る

Python で外部入出力を扱う代表的な関数といえば open です。この関数は指定する mode などの引数によって性質の大きく異なったオブジェクトを返します。 この挙動について把握するにはまず io モジュールで定義されているクラスについて知っておく必要があります。

Python の入出力に関するオブジェクトは以下の 3 種類に大別されます(io モジュールのリファレンスに書いてあることの要約です)。

  • raw I/O
    • OS に近い部分とやり取りする生の入出力(別名: unbuffered I/O)
    • raw I/O の具象クラスは RawIOBase 抽象基底クラスを継承・実装する
    • read() メソッドの戻り値、write(b) メソッドの引数は bytes
    • 例: FileIO
      • open 関数でバイナリモードかつ buffering0 を指定するとこれが返ってくる
  • バイナリ I/O
    • バッファを持つ入出力(別名: buffered I/O)
    • バイナリ I/O の具象クラスは BufferedIOBase 抽象基底クラスを継承・実装する
    • read() メソッドの戻り値、write(b) メソッドの引数は bytes
    • 基本的に raw I/O のオブジェクトを内包している
    • 例: BufferedReaderIO, BufferedWriterIO, BufferedRandomIO
      • open 関数でバイナリモードかつ buffering0 以外を指定するとこれが返ってくる
    • インメモリのバイナリ I/O として BytesIO が存在する
  • テキスト I/O
    • テキストとバイナリの変換機構を持つ入出力
    • テキスト I/O の TextIOBase 抽象基底クラスを継承・実装する
    • read() メソッドの戻り値、write(s) メソッドの引数は str
    • 基本的にバイナリ I/O(まれに raw I/O)のオブジェクトを内包している
    • 例: TextIOWrapper
      • open 関数でテキストモードを指定するとこれが返ってくる
    • インメモリのテキスト I/O として StringIO が存在する

io モジュールの各種クラスを図示すると以下のようになります。

io モジュールのクラス図

open 関数の引数

組み込みの open 関数は以下のようなシグネチャを持ちます。以下、この引数の意味とそれを指定した際の挙動について説明していきます。

open(
    file,
    mode='r', buffering=-1,                    # モードとバッファリング
    encoding=None, errors=None, newline=None,  # テキスト I/O に関する設定
    closefd=True, opener=None,                 # raw I/O に関する設定
)
モードとバッファリング

open 関数の mode として指定できる文字は(非推奨のものを除くと)現在 7 種類あります。 これは「raw IO に関する指定」「バイナリ/テキストに関する指定」の 2 種類に分けられます。

「raw IO (FileIO) に関する指定」は以下の 5 つです。このうち + を除く r, w, a, x については必ずどれか 1 つだけを指定する必要があります。

文字 意味
r 読み込み用。ファイルが存在しなければエラーとする。
w 書き込み用。ファイルが存在しなければ作成する。ファイルが既に存在すれば内容を 0 バイトに切り詰める。
a 書き込み用。ファイルが存在しなければ作成する。ファイルが既に存在すれば書き込みは末尾への追記とする。
x 書き込み用。ファイルが存在しなければ作成する。ファイルが既に存在すればエラーとする。
+ 読み込み・書き込みの両方を可能にする。

「バイナリ/テキストに関する指定」は以下の 2 つです。何も指定しないと t となります。

文字 意味
b バイナリとして読み込む(バイナリ I/O もしくは raw I/O を返す)
t テキストとして読み込む(テキスト I/O を返す)

open 関数の返すファイルオブジェクトのクラスは modebuffering の値によって以下のような流れで決まります。 なおテキストモードかつ buffering を 0 とすることはできません。

  1. file(ファイルパス)と mode (r, w, a, x, +) を用いて raw I/O (FileIO) を開く
  2. buffering の値によってバイナリ I/O を利用するかどうかを決める
    • 0 の場合 raw I/O をそのまま使う
    • 0 でない場合 raw I/O をバイナリ I/O でラップする
      • バイナリ I/O の種類は mode の値によって分岐する:
        • + を含む場合 → BufferedRandom
        • r を含む場合 → BufferedReader
        • w, a, x を含む場合 → BufferedWriter
  3. mode の値によってテキスト I/O を利用するかどうかを決める
    • b を含む場合 raw I/O・バイナリ I/O をそのまま使う
    • b を含まない(t を含む)場合 raw I/O・バイナリ I/O をテキスト I/O (TextIOWrapper) でラップする
テキスト I/O に関する設定

encoding, errors, newlineTextIOWrapper の挙動を決める引数です。

  • encoding: 文字列のエンコーディング
    • デフォルトでは locale.getpreferredencoding() の値が利用される
    • codecs モジュールのリスト に存在するテキスト-バイナリ変換を利用できる
  • errors: 文字列をデコードできなかった場合の処理モード
    • デフォルトは "strict"(変換できない文字が出現した場合エラーとする)
    • "ignore" はエラーを無視する
    • "replace", "xmlcharrefreplace", "backslashreplace", "namereplace" は変換できない文字を指定の方法で置き換える
    • "surrogateescape" は変換できない文字を壊さずに保持でき、encode(errors="surrogateescape") することで元のバイト列に戻せるという性質がある
  • newline: 改行コードに関する処理モード
    • デフォルト (None) では universal newlines モードが有効になり、入出力に含まれる改行を "\n" と相互に自動変換する
      • この場合、例えば Windows では入力に含まれる "\r\n" (CR+LF) は Python 側で "\n" (LF) に変換され、出力する際は "\n""\r\n" に変換される
    • "" を指定した場合は入力の "\r\n", "\n", "\r" を行末として扱うが "\n" に自動変換はせず、出力時に "\n" を自動変換することもない
    • "\n", "\r", "\r\n" のいずれかを指定した場合は入力時に指定の文字列のみを行末として扱い、出力時に "\n" を指定の文字に自動変換する
raw I/O に関する設定

closefd, opener は raw I/O の作成と後処理に関する引数です。一般的な用途ではあまり使うことはないと思われます。

  • closefd: ファイルディスクリプタを閉じるか否か
    • デフォルトは True (閉じる)
    • sys.stdin, sys.stdout, sys.stderr では False となっており、close() メソッドを呼び出した際にファイルディスクリプタ (0, 1, 2) が閉じないようになっている
  • opener: ファイルディスクリプタを開く関数
    • デフォルトは None で、os.open を渡すのと同様の挙動(この関数は open 系システムコールのラッパーと考えてよい)

sys.stdin / sys.stdout / sys.stderr の正体

sys.stdin, sys.stdout, sys.stderr は大まかには以下のように初期化されます(CPython 実装ではこの初期化は Python ではなく C 言語で書かれています。pylifecycle.ccreate_stdio 関数 が該当箇所です)。

sys.stdin = io.TextIOWrapper(
    io.BufferedReader(
        io.FileIO(0, "r", closefd=False),
    ),
    line_buffering=os.isatty(0),
)
sys.stdout = io.TextIOWrapper(
    io.BufferedWriter(
        io.FileIO(1, "w", closefd=False),
    ),
    line_buffering=os.isatty(1),
)
sys.stderr = io.TextIOWrapper(
    io.BufferedWriter(
        io.FileIO(2, "w", closefd=False),
    ),
    line_buffering=True,
)

アプリケーションと OS とのやり取りの中ではファイルはファイルディスクリプタという識別子(番号)を用いて管理されており、ファイルディスクリプタ 0, 1, 2 はそれぞれ標準入力・標準出力・標準エラー出力に割り当てられています。 このファイルディスクリプタを FileIO の引数として与えることで Python のファイルオブジェクト (raw I/O) として扱うことができます。標準入力・標準出力が端末である場合(ファイル等にリダイレクトされていない場合)は TextIOWrapperline_bufferingTrue となります(標準エラー出力では常に True です)。

なお、Windows の Python 3.6 以降では FileIO の代わりに専用のコンソール用の実装が利用されます。この挙動を無効化するのが PYTHONLEGACYWINDOWSSTDIO 環境変数です。

PYTHONUNBUFFERED 環境変数が空でない場合、もしくは処理系のコマンドラインオプションで -u を指定した場合は以下のように出力にバッファを使わなくなります(入力は引き続きバッファされます)。

sys.stdin = io.TextIOWrapper(
    io.BufferedReader(
        io.FileIO(0, "r", closefd=False),
    ),
)
sys.stdout = io.TextIOWrapper(
    io.FileIO(1, "w", closefd=False),
    write_through=True,
)
sys.stderr = io.TextIOWrapper(
    io.FileIO(2, "w", closefd=False),
    write_through=True,
)

ファイルオブジェクトの内容のコピー

shutil.copyfileobj でファイルオブジェクトの内容をコピーできます。 コピー元・コピー先には BytesIOStringIO 等も使えます(shutil モジュールにあるのがかなりミスリーディングですが……)。

>>> src = io.StringIO("あいう")
>>> dest_bin = io.BytesIO()
>>> dest = io.TextIOWrapper(dest_bin, encoding="utf-8")
>>> shutil.copyfileobj(src, dest)
>>> dest.flush()
>>> dest_bin.getvalue()
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'

標準ライブラリでのファイルオブジェクトの使われ方

標準ライブラリの以下の関数・メソッドはファイルオブジェクトを返します。

  • urllib.request.urlopen(url) 関数
  • ソケットオブジェクトmakefile() メソッド
  • gzip.open(filename), bz2.open(filename) 関数
  • zipfile.ZipFile オブジェクトの open(name) メソッド
  • tarfile.TarFile オブジェクトの extractfile(member) メソッド

以下の関数・メソッドはファイルオブジェクトを引数に取ります。

  • json.load(fp) / json.dump(obj, fp) 関数
    • json.load(fp)json.loads(fp.read()) と同義(全バイトをメモリ上に一度に読み込む)
    • json.load(fp) は内容が UTF-8, UTF-16, UTF-32 のいずれかであればバイナリ I/O も受け付ける(Python 3.6 以降)。一方 json.dump(obj, fp) はテキスト I/O しか受け付けない
  • csv.reader(csvfile) / csv.writer(csvfile) 関数
    • csv.reader(csvfile) 関数は文字列のイテレータ、csv.writer(csvfile) 関数は write メソッドを持つオブジェクトを引数に取る
    • なお内包するファイルオブジェクトの Universal newline モードについては newline="" (改行コードの自動変換を行わない)としておくことが推奨されている
  • urllib.request.urlopen(url) 関数(data 引数)
  • tarfile.open 関数(fileobj 引数)、TarFile.addfile メソッド(fileobj 引数)

このように標準ライブラリにはファイルオブジェクトを利用するメソッドが多くあります。 自分で実装するメソッドについてもファイルオブジェクトを受け取る・返すようにしておくとこれらのメソッドを簡単に利用することができ、また効率のよいストリーム処理も可能になるかもしれません。

おまけ: 高度な Tips

io_io の関係について

CPython の場合 io モジュールのクラスは以下のように C 言語実装(_io モジュール)を使う形となっています。

  • io モジュールの具象クラスは _io モジュールのクラスをそのまま使う
    • クラス名が _io.TextIOWrapper のように表示されるのはこのため
  • io モジュールの IOBase のような 抽象基底クラス (ABC)_io モジュールのクラスを継承する
  • ABC の register メソッドを使うことで一部の継承関係を後付けする

下図の灰色の矢印が実際の(__bases____mro__ で得られる)継承関係、赤い矢印が ABC によって後付けされた継承関係です。

CPythonにおける継承関係

Python の open 関数と open システムコールの対応

Python の open 関数は内部で os.open 関数を利用しており、これは open 系システムコール(以下 open(2) と記載)のラッパーとなっています 1 。Python の openmode 引数と読み込み・書き込み権限に関するフラグとの対応は以下の通りです(CPython 実装を参照しています)。

Python の mode open(2) のフラグ
r O_RDONLY
w. a, x O_WRONLY
r+, w+, a+, x+ O_RDWR

ファイル存在時・非存在時の挙動に関するフラグとの対応は以下の通りです。

Python の mode open(2) のフラグ
w O_CREAT | O_TRUNC
a O_CREAT | O_APPEND
x O_CREAT | O_EXCL

その他の細かい挙動は以下の通りです。

  • デフォルトではファイルディスクリプタを子プロセスへ継承させないためのフラグが付与される
    • Linux 2.6.23 以降など O_CLOEXEC が利用できる場合は常に利用する
    • Windows の場合 O_NOINHERIT を常に利用する
  • Windows の場合 O_BINARY が常に付与される
    • Python 側でテキストモードを指定した場合も OS レベルではバイナリモードを使用する
  • Windows ではファイルパス引数としてワイド文字列を取る _wopen 関数が内部で使われる

他言語との比較

Python の I/O 関係のクラスを Java / Go のクラス・インターフェースと比較すると以下のようになります(あくまでざっくりとした比較であり言語によってインターフェースや内部実装はかなり異なります)。 Python のクラスは便宜上読み取り (R) / 書き込み (W) の機能別に分離しています。

Python Java Go
io.RawIOBase (R)
io.BufferedIOBase (R)
java.io.InputStream io.Reader
io.RawIOBase (W)
io.BufferedIOBase (W)
java.io.OutputStream io.Writer
io.FileIO (R) java.io.FileInputStream
java.io.RandomAccessFile
os.File
io.FileIO (W) java.io.FileOutputStream
java.io.RandomAccessFile
os.File
io.BufferedReader java.io.BufferedInputStream bufio.Reader
io.BufferedWriter java.io.BufferedOutputStream bufio.Writer
io.BytesIO (R) java.io.ByteArrayInputStream bytes.Buffer
io.BytesIO (W) java.io.ByteArrayOutputStream bytes.Buffer
io.TextIOBase (R) java.io.Reader -
io.TextIOBase (W) java.io.Writer -
io.TextWrapper (R) java.io.InputStreamReader
java.io.Scanner
bufio.Reader
bufio.Scanner
transform.Reader
io.TextWrapper (W) java.io.OutputStreamWriter bufio.Writer
transform.Writer
io.StringIO (R) java.io.StringReader strings.Reader
io.StringIO (W) java.io.StringWriter strings.Builder
  • Java や Go では基本的に読み取りに利用するクラス・インターフェースと書き込みに利用するクラス・インターフェースが分かれている
  • Go ではテキスト I/O 専用のインターフェースはほぼ用意されていない
    • Go の文字列 (string) は任意のバイト列を保持する([]byte との変換コストが低い)設計になっており Python, Java とは大きく異なる
    • 1 文字 (rune) 毎に返すインターフェースとしては io.RuneReader が存在する
    • transform.Writer / transform.Readergolang.org/x/text/transform の interface であり、実際にはこれを実装した各エンコーディング毎の型を利用することになる

おわりに

今回は Python の open 関数と io モジュールについて解説しました。 open は入門書にも出てくるような基本的な関数ではありますが、細かい挙動を見ていくとかなり様々なことをやっているという印象で全体を理解するのはなかなか大変でした。 しかしながらこのような理解をしていなくてもなんとなく使えるようにもなっており、その意味では Python の設計の丁度よさ・割り切り方というのを感じ取ることもできました。

Python の公式リファレンスはかなり充実していおり、既に使っている機能でも読み返してみると新たな発見があります。 少しでも挙動が気になったら是非リファレンスに立ち返ってみましょう。

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。

新卒採用 キャリア採用|株式会社朝日ネット


  1. 厳密には libc の関数 open(3) を Python で利用するためのラッパーとなっており、glibc 等の内部実装では openat(2) システムコールが呼ばれている場合もあります。