Python I/Oの再考(1): ストリームの種類と比較検証

## はじめに

PythonのI/O操作は選択肢が多く、コーディング時、
どのクラスのメソッドを使うべきか、迷う場合あります。
自己メモ兼ねて、I/Oの全体像をまとめてみました。

## I/Oのベースは`ファイルオブジェクト`(`ストリーム`、`file-likeオブジェクト`と同義)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/a0b9ce39-0290-ea14-7305-6352fa89fd4e.png)

### ファイルオブジェクトの定義を[Python用語集](https://docs.python.org/ja/3/glossary.html#term-file-object)から抜粋

- 下位リソースに対するファイル指向APIを提供
    - `read()`メソッド
    - `write()`メソッド
- 下記リソースへのアクセス手段を提供
    - ディスク上ファイル
    - その他ストレージ
    - 通信デバイス
        - 標準入出力
        - インメモリバッファ
        - ソケット
        - パイプ

### ファイルオブジェクトの種類

- Text File
    - テキストファイル
- Buffered Binary File
    - バッファリング付きバイナリファイル
- Raw Binary File
    - バッファリングなしバイナリファイル

### ファイルオブジェクト操作のインターフェス

`io`モジュールに定義されている

## `io`モジュールの定義を[Pythonドキュメント](https://docs.python.org/ja/3/library/io.html)から抜粋

- ストリーム(=ファイルオブジェクト)を扱うコアツールで、様々な種類のI/Oを提供
    - テキストストリームの`Text I/O`
        -  `str`オブジェクトを受け取り(INPUT)、`str`オブジェクトを生成(OUTPUT)
        -  データのエンコード・デコードを行う
        -  改行文字変換を行う(オプショナル。プラットフォーム依存)
        -  テキストストリーム作成サンプル
            -  `f = open("myfile.txt", "r", encoding="utf-8")`
    - バイナリストリームの`Binary I/O`(Buffered I/O)
        - `bytes-like`オブジェクトを受け取り(INPUT)、`bytes`オブジェクトを生成(OUTPUT)
        - データのエンコード、デコードを行わない
        - 改行文字変換を行わない
        - 非テキストデータだけでなく、テキストデータを自前処理したい場合も使用
        - バイナリストリームの作成サンプル
            - `f = open("myfile.jpg", "rb")`
    - Rawストリームの`Raw I/O`(Unbuffered I/O)
        - バイナリストリームやテキストストリームの低レベル部品
            - ユーザーコードで直接扱うべき場面は滅多にない
        - Rawストリームの作成サンプル(バッファリングを無効にし、ファイルをバイナリーモードで開く)
            - `f = open("myfile.jpg", "rb", buffering=0)`
- ストリーム操作モード
    - 読み込み専用
    - 書き込み専用
    - 読み書き
- ストリームアクセス方向
    - ランダムアクセス
        - 前方、後方の任意の場所にシーク
    - シーケンシャルアクセス
        - 一方向のみ(例えば、ソケットやパイプなど)
- ストリームのデータ型
    - バイナリストリームは、`bytes`オブジェクトのみサポート
    - テキストストリームは、`str`オブジェクトのみサポート

## ストリームI/Oの検証

目的は、各種ストリームのINPUT、OUTPUTを確認。

### テキストストリーム(`Text I/O`)

- テキストファイル`test_file`を作成

中身は漢字2文字(文字コードはUTF-8)
`検証`

- test_fileを読み込み専用、テキストモードで開く、

```python
> python
>>> f = open("test_file", "r", encoding="utf-8")
```

openされたテキストストリームのタイプを確認すると、
`TextIOWrapper`クラスとなっています。

```python
>>> type(f)
<class '_io.TextIOWrapper'>
```

ストリームを読み込むと、そのデータタイプは、
`str`クラスとなっています。

```
>>> data = f.read()
>>> type(data)
<class 'str'>
```

読み込まれたデータを出力すると、
漢字2文字`検証`が出力されます。

```
>>> print(data)
検証
```

### バイナリストリーム(Binary I/O)

- test_fileを読み込み専用、バイナリモードで開く、

```python
> python
>>> f = open("test_file", "rb")
```

openされたテキストストリームのタイプを確認すると、
`BufferedReader`クラスとなっています。

```
>>> type(f)
<class '_io.BufferedReader'>
```

ストリームを読み込むと、そのデータタイプは、
`bytes`クラスとなっています。

```
>>> data = f.read()
>>> type(data)
<class 'bytes'>
```

読み込まれたデータを出力すると、
16進で6バイト出力されます。

```
>>> print(data)
b'\xe6\xa4\x9c\xe8\xa8\xbc'
```

漢字2文字`検証`のUTF-8コードを確認すると、
※ 参照: [Unicode対応 文字コード表](http://ash.jp/code/unitbl21.htm)

`検`: `E6A49C`
`証`: `E8A8BC`

となり、上記バイト列と一致していることがわかりました。

### Rawストリーム(Raw I/O)

- test_fileを読み込み専用、バッファリングを無効に、バイナリモードで開く、

```python
> python
>>> f = open("test_file", "rb", buffering=0) 
```

openされたテキストストリームのタイプを確認すると、
`FileIO`クラスとなっています。

```
>>> type(f)
<class '_io.FileIO'>
```

ストリームを読み込むと、そのデータタイプは、
`bytes`クラスとなっています。

```
>>> data = f.read()
>>> type(data)
<class 'bytes'>
```

読み込まれたデータを出力すると、
16進で6バイト出力されます。

```
>>> print(data)
b'\xe6\xa4\x9c\xe8\xa8\xbc'
```

漢字2文字`検証`のUTF-8コードと一致します。
バイナリストリームと同じ結果となりました。

## おわりに

Pythonのストリーム種類とI/Oを検証してみました。
バッファリングあり・なしの違いが気になりますが、
次回のお楽しみとさせてください。

##### [次回] [Python I/Oの再考(2): ioモジュールの高レベルインターフェース](https://qiita.com/mingchun_zhao/items/3206250501d9c8ac69e3)