LoginSignup
3
2

More than 1 year has passed since last update.

Python I/Oの再考(3): I/Oストリームのクラス階層

Last updated at Posted at 2022-08-21
[前回] Python I/Oの再考(2): ioモジュールの高レベルインターフェース

はじめに

前回は、ioモジュールの高レベルインターフェースを理解しました。
今回は、ioモジュールのクラス群です。

抽象基底クラス(ABC, Abstract Base Class)とは

  • インターフェースを定義する方法を提供
    • 具象クラスの実装に役立つデフォルトの実装を提供
  • 仮想サブクラスを導入可能
    • 他のクラスの継承ではないが、isinstance()issubclass()により認識可能

ioモジュールが提供する抽象基底クラス(ABC)と階層

  • IOBase: I/O階層の最上位、全I/Oクラスのベースクラス
    • RawIOBase: IOBaseを拡張、Rawバイナリストリーム操作用
    • BufferedIOBase: IOBaseを拡張、バッファリングバイナリストリーム操作用
    • TextIOBase: IOBaseを拡張、テキストストリーム操作用

抽象基底クラスと具象クラスの関係

  • 抽象基底クラスABC(ストリームのカテゴリ分類に使用)
    • 具象クラス1(標準のストリームを実装)
    • 具象クラス2(標準のストリームを実装)
    • ... ...

I/Oストリームのクラス階層

  • IOBase: ストリームの基本インターフェースを定義、デフォルトは読み込み/書き込み/シーク不能
    • RawIOBase: IOBaseを継承、Rawバイナリストリームでbytesの読み書きを行う
      • FileIO: RawIOBaseを継承、ファイルシステムのファイルへのインターフェースを提供
    • BufferedIOBase: IOBaseを継承、バッファリングバイナリストリームで、RawIOBaseのRawバイナリストリームへの高レベルアクセスを提供
      • BufferedReader: BufferedIOBaseを継承、読み込み可能かつシーク不能なRawバイナリストリームへのバッファリングインターフェースを提供
      • BufferedWriter: BufferedIOBaseを継承、書き込み可能かつシーク不能なRawバイナリストリームへのバッファリングインターフェースを提供
      • BufferedRandom: BufferedReaderとBufferedWriterを継承、シーク可能なRawバイナリストリームへのバッファリングインターフェースを提供
      • BufferedRWPair: BufferedIOBaseを継承、シーク不能なRawバイナリストリームへの二つのバッファリングインターフェースを提供(それぞれ読み込みと書き込み)
      • BytesIO: BufferedIOBaseを継承、バイナリストリームで、インメモリbytesバッファを使用
    • TextIOBase: IOBaseを継承、テキストストリームのベースクラス
      • TextIOWrapper: TextIOBaseを継承、テキストストリームでBufferedIOBaseのバッファリングバイナリストリームへの高レベルアクセスを提供
      • StringIO: TextIOBaseを継承、テキストストリームで、インメモリテキストバッファを使用

FileIOクラスとBufferedReaderクラスの違いを検証

FileIOは、RawIOBase(バッファリングなし)を継承し、
BufferedReaderは、BufferedIOBase(バッファリングあり)を継承します。

何がどう違うか、AlmaLinux環境で検証してみます。

FileIOクラスによるファイル読み込みを確認

  • ファイルtest_big_fileを作成

ファイル中身は任意。
ファイルサイズは6020バイトとなっています。

$ ls -l test_big_file
-rw-rw-r-- 1 zhao zhao 6020  8月 21 11:42 test_big_file
  • Pythonスクリプト作成
    • ファイルを、バッファリングを無効にし、バイナリモードで開く
    • 5120バイトを読み込む
test_fileio.py
with open("test_big_file", "rb", buffering=0) as f:
    print(type(f))
    data = f.read(5120)
    print(type(data))
    print(len(data))
  • Pythonスクリプトを実行
    • straceコマンドでシステムコールを採取
$ strace -f -o strace_fileio.log python test_fileio.py
  • straceログstrace_fileio.logを開き
    • test_big_fileファイルに対するシステムコールを確認
openat(AT_FDCWD, "test_big_file", O_RDONLY|O_CLOEXEC) = 3
read(3, "\346\244\234\350\250\274\346"..., 5120) = 5120

システムコールread()は1回のみ実行される。
ソースコードのf.read()メソッドで指定した5120バイトを、
バイト単位でびったり読み込んでくれています。
FileIOクラスの読み込み単位は、1バイトであることがわかります。

BufferedReaderクラスによるファイル読み込みを確認

  • Pythonスクリプト作成
    • ファイルをバイナリモードで開く
    • 5120バイトを読み込む
test_buffered-reader.py
with open("test_big_file", "rb") as f:
    print(type(f))
    data = f.read(5120)
    print(type(data))
    print(len(data))
  • Pythonスクリプトを実行
    • straceコマンドでシステムコールを採取
$ strace -f -o strace_buffered-reader.log python test_buffered-reader.py
  • straceログstrace_buffered-reader.logを開き
    • test_big_fileファイルに対するシステムコールを確認
openat(AT_FDCWD, "test_big_file", O_RDONLY|O_CLOEXEC) = 3
read(3, "\346\244\234\350\250\274\346"..., 4096) = 4096
read(3, "\350\250\274\346\244\234\350"..., 4096) = 1924
close(3)

システムコールread()が2回実行されました。
1回目は、4096バイト(バッファサイズ分)を読み込み、
2回目は、1924バイトを読み込んでいます。
合わせると、4096+1924=6020バイト
あれ、ソースコードのf.read()メソッドで指定した5120バイトではなく、
ファイルサイズ6020バイト分をすべて読み込んでいます。

つまり、BufferedReaderクラスの読み込み単位は、
バッファサイズ(4096バイト)であることがわかります。

おわりに

I/Oストリームのクラス階層とそれぞれの違いを理解しました。
次回も続きます。お楽しみに。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2