Python のファイルアクセス



こんにちは、切口太郎です。

ファイルアクセスは、どのような言語でも必ず操作する基本技術です。

Python の組み込み関数のファイル入出力(ローカルディスクへの入出力)についてご紹介します。

Python には、いろいろなフレームワークやライブラリーがあって、かなり高度なファイルへのアクセスができますし、ファイルアクセスのようにネットワーク先のディスクへアクセスしたりできるライブラリーもあります。

いきなり、高度なファイルアクセスを学ぶといろいろな概念を同時に覚えないといけないので、まずは基本の組み込み関数のファイルアクセスを覚えましょう!

1 ファイルアクセスの基本操作


Python の組み込み関数のファイルアクセスは、低水準と言われるOSが提供しているファイル I/O APIをそのまま Python で実行できるようになっています。

ファイルアクセスの基本的な操作は下表の関数を利用することで行います。

Python3
説明
バイナリ
ファイル(*1)
テキスト
ファイル(*1)
補足
open
ローカルディスクのファイルを開く

write
オープンしたファイルの参照点へ出力する。

read
オープンしたファイルの参照点から入力する。

seek
オープンしたファイルの参照点を移動する
textファイルでもできるが、バイナリーファイルのアクセスに利用される。
close
オープンしたローカルディスクをクローズする。


(*1) テキストファイルとバイナリーファイルについて

プログラムでファイルにアクセスする場合、バイナリーファイルとテキストファイルに気をつける必要があります。
バイナリーファイルは、プログラムファイルのようにテキストとしては表示できません。
テキストファイルは、UTF-8 / Shift_JIS / EUC などのエンコード体系に従ったデータのみで構成されているファイルで、エンコード体系に従った方法でテキストとして表示できます。

Python では、ファイルの種類は、open 関数で指定します。

Open関数
順序
名前
初期値
必須

第1引数
file
なし
オープンするファイル名
第2引数
mode
'r'
ファイルをどの形式で利用するかを指定する。
(*2)の表を参照
第3引数
buffering
-1
ファイルアクセスのバッファリング方法の指定
0:バッファリングなし(modeがバイナリーのみ)
1:1行単位のバッファリング(modeがテキストのみ)
2-xxxx : 固定長バッファサイズ(バイト)を指定
-1:自動
第4引数
encode
None
テキストファイルの時にエンコードを指定する。
対象のファイルのエンコード体系と一致させる。
第5引数
errors
Non
テキストファイルのエンコードエラー時に指定するエラーハンドラーを指定する。
(*3)の表を参照
第6引数
newline
None
テキストファイルの改行文字の指定。
None, '', '\n', '\r', '\r\n' のいずれかを指定 (*4)
第7引数
closefd
True
初期値のままでいいので省略して下さい。
第8引数
opener
None
カスタムオープナーを指定を指定します。
この引数は、フレームワークなどの応用ライブラリーを作成するぐらいしか使用しません。省略してください。

(*2) ファイルオープンモード

モード(文字)
説明
r
読み込み用に開く (デフォルト)
w
書き込み用に開き、まずファイルを切り詰める
x
排他的な生成に開き、ファイルが存在する場合は失敗する
a
書き込み用に開き、ファイルが存在する場合は末尾に追記する
b
バイナリモード
t
テキストモード (デフォルト)
+
ディスクファイルを更新用に開く (読み込み/書き込み)

ファイルオープンモードは、文字列で指定して複数指定ができます。
例)'rt' 


(*3) エラーハンドラー

None
エンコーディングエラーがあると例外 ValueError を発生させます。
static
エンコーディングエラーがあると例外 ValueError を発生させます。
ignore
エラーを無視します。エンコーディングエラーを無視することで、データが失われる事があります。
replace
不正な形式のデータが存在した場所に('?' のような) 置換マーカーに置換します。
surrogateescape
正しくないバイト列を、Unicode の Private Use Area (私用領域) にある U+DC80 から U+DCFF のコードポイントで示します。
xmlcharrefreplace
ファイルへの書き込み時のみサポートされます。そのエンコーディングでサポートされない文字は、&#nnn; 形式の適切な XML 文字参照で置換されます。
backslashreplace
不正なデータを Python のバックスラッシュ付きのエスケープシーケンスで置換します。
namereplace
サポートされていない文字を \N{...} エスケープシーケンスで置換します。

通常は、ハンドラーを指定しません。レガシーシステム(古いシステム)からのデータを読むときには、データを変換してからPythonで読むのが一般的な方法です。

(*4) newline
テキストファイルの改行文字の事です。

Linux/macOS 系は標準で '\n' (LF) コードを改行文字として利用し、Windowsは '\r\n' (CR-LF) を改行コードとして利用しています。


2 シーケンシャルアクセスとランダムアクセス


ファイルのアクセス方法大きく分けて、シーケンシャルアクセスとランダムアクセスがあります。

アクセス方法
説明
バイナリファイル
テキストファイル
補足
シーケンシャルアクセス
ファイルを先頭から順に終端までアクセスする方法
主にテキストファイルで使用されます
ランダムアクセス
ファイルのアドレス(*5)を指定して、バイト単位でデータをアクセスする方法
△(*6)
バイナリーファイルでの利用が主です。

(*5) ファイルのアドレスとは、先頭を0として、バイト単位の連続番号です。

(*6) テキストファイルでランダムアクセスの利用

Python はテキストファイル、バイナリーファイルの区別無く、ランダムアクセスができますが、実際にはバイナリーファイルで利用します。
データベースのデータファイルなどは、ランダムアクセスを使用しています。

レガシーシステムなどは、テキストファイルも固定長フォーマットで出力するものがあるので、このようなファイルを読む時にランダムアクセスを利用します。
(汎用機という昔のコンピュータが固定長ファイルを出力するケースが多かったのですが、もう10年ぐらい前の話です)

これから始める人はランダムアクセス = バイナリーファイルと覚えて間違いないと思います。

Python でテキストファイルのシーケンシャルアクセスを説明します。

from os.path import expanduser

#テキストのシーケンシャルライト
def write_text(filename , text):
    # with を使用すると、そのスコープ内で、ファイルがオープンされる。
    # with スコープを抜けると、自動でクローズされる。
    with open(filename, mode='wt' ,  encoding='utf-8') as filep:
        size = filep.write(text)

    return size

#テキストのシーケンシャルリード
def read_text(filename):
    text = ''
    with open(filename , mode="rt" , encoding='utf-8') as filep:
        lines = filep.readlines()
        for line in lines:
                text += line
    
    return text
# 呼び出しプログラム

# テストデータ
text = '''本日はとってもお日柄が良く、お二人の門出を祝福するような暖かい日です。'''

# パスの設定は、linux ライクで / がセパレータ
path_ascii = expanduser('~') + '/work/test.txt'

print("----------- テキスト シーケンシャル入出力 ----------")
print("write text  length : " + str(len(text)))
print("text write : " + str(write_text(path_ascii, text)))
print("read text : " , read_text(path_ascii))

実行すると、下記のように出力します。

----------- テキスト シーケンシャル入出力 ----------
write text  length : 35
text write : 35
read text :  本日はとってもお日柄が良く、お二人の門出を祝福するような暖かい日です。



このプログラムを実行する前には、予めユーザーのホームディレクトリー(フォルダー)に work という名前のディレクトリー(フォルダー)を作成して下さい。

ソースの説明です。

4行目がシーケンシャルファイルの出力、13行目がシーケンシャルフィアルの入力です。
呼び出しは、30行目から始まります。

30行目は、出力するテキストの文字列長を表示します。
31行目で write_text() 関数を呼び出します。

4行目の write_text() 関数では、 with を使用してファイルをオープンします。
 with を使用するとブロックを抜けた場合、自動的にファイルをクローズします。

このように記述するのと同じことです。
filep = open(filename, mode='wt' ,  encoding='utf-8')
size = filep.write(text)
filep.close()

open のパラメータは、
①出力先のファイル名
②ファイルモードは、「ライトテキスト」
③文字エンコードは、utf-8 を指定

Python に限らず、マルチプラットフォーム(複数のOSで動作することです)対応の文字セットは、UTF-8 を利用します。

8行目でテキストをファイルに出力しています。Pythonの出力関数は、実際に出力したテキストの文字数を返します。
出力結果を見ると、出力する文字列は、35文字、実際に出力した文字数は、35文字で正しく出力しているのを確認できます。

16行目は、出力したファイルを読み出します。
出力結果を見ると、出力用に用意した文字列と同じ文字列を読み出していることが確認できます。

次に、バイナリーファイルのシーケンシャルアクセスの例です。

from os.path import expanduser

# バイナリーデータのシーケンシャルライト
def write_data(filepath , data):
    with open(filepath , "wb") as filep:
        size = filep.write(data)
    return size

# バイナリーデータのシーケンシャルリード
def read_data(filepath):
    with open(filepath , 'rb') as filep:
        data = filep.read()
    return len(data)

# 呼び出しプログラム

# バイナリデータ
data = "本日はとってもお日柄が良く、お二人の門出を祝福するような暖かい日です。".encode('utf-8')

# パスの設定は、linux ライクで / がセパレータ
path_bin = expanduser('~') + '/work/area.dat'

print("\n----------- バイナリ シーケンシャル入出力 ----------")

print("write data  length : " + str(len(data)))
print("write binary  : "  + str(write_data(path_bin,data)))
print("read binary : " , read_data(path_bin))

実行するとこのような出力をします。

----------- バイナリ シーケンシャル入出力 ----------
write data  length : 105
write binary  : 105
read binary :  105

先程のテキストファイルのシーケンシャルアクセスとほぼ同じです。

ソースの説明です。

18行目で、文字列を UTF-8 でエンコードしてバイトデータに変換しています。 変換後のdata の型は、bytes です。

5行目でバイナリーファイルをオープンしています。
モードが、"wb" 「ライトバイナリー」にしています。
また、テキストファイルではないので、エンコーディングの指定はしていません。

11行目の オープンも "rb" 「リードバイナリー」モードを指定しています。

25行目に data 変数のサイズを表示しています。こちらは同じ文字列を使用していますが、butes データ型なので、文字数ではなく、バイト数で表示しています。
6行目のwrite() 関数の戻り値もバイナリーファイルなので、105バイト出力が返っていますので、正しく出力できていることを確認できます。

12行目の read()関数はバイナリーデータを読み込んでいます。関数の戻り値は、bytes 型になっています。
13行目で、戻り値のバイト長を取得しています。表示では 105バイトが読めているのが確認できるので、正しく処理ができていることが確認できます。

Python では、シーケンシャルアクセスは簡単に短いコードで作成することができます。

ランダムアクセスです。
ランダムアクセスはこのように、ファイルの先頭から終端までの任意の位置を自由にアクセスできます。

import os

#ランダムアクセス
def randam_access(filepath , max):
    # 出力データ 0 から 0xff までのバイトデータを作成する
    b = bytes([value for value in  range(0 , max)])
    # ファイルのオープン、read/write binary の場合には、rb+ を指定する。
    # bをつけないとテキストモードとしてオープンされるのでバイナリーデータは使用できない。
    with open(filepath , 'rb+') as filep:
        #初期データの出力
        filep.seek(0 , 0)
        filep.write(b)

        # seek(offset , position)
        # position は、0が先頭、1が現在のポジション、2がファイルの終端から
        # offset は positio からの offset (バイト)

        # 先頭の読み出し
        filep.seek(0 , 0)
        print("先頭データ : " , filep.read(1))

        # 最終の読み出し
        filep.seek(-1 , 2)
        print("終端データ : " , filep.read(1))

        # 真ん中の読み出し
        center = int(max / 2)
        filep.seek(center , 0);
        print("中央データ : " , filep.read(1))

# ユーザーのホームディレクトリーを取得 (windows/linux 両方ともいける)
from os.path import expanduser

# テストテキスト
text = '''本日はとってもお日柄が良く、お二人の門出を祝福するような暖かい日です。'''

# パスの設定は、linux ライクで / セパレータ
path_area = expanduser('~') + '/work/area.dat'

print("ランダムアクセス")
randam_access(path_area , 256)

実行結果はこうなります。
ランダムアクセス
先頭データ :  b'\x00'
終端データ :  b'\xff'
中央データ :  b'\x80'

ソースの説明です。

6行目で、bytes に 0 から 255(0xff) までの配列を作成しています。
9行目で、バイナリーファイルをオープンしています。モードの "rb+" は、バイナリーファイルでランダムアクセスする場合のモードです。ファイルが存在しない場合には作成してくれます。

11,12行目は、6行目に作成した初期データをディスク上に出力しています。
11行目の seek は、ファイルの参照点を先頭に移動しています。参照点とは、ディスク上のアドレスです。
Pythonのファイルアクセス(read/write) などは、すべて参照点から開始されます。
参照点の事を、「カレントポイント」と呼んだりもします。

seek のパラメータは、次のようになっています。

順序
名前
初期値
必須

第1引数
offset
なし
参照点からのオフセットを指定する。
第2引数
from_what

'r'
0 : ファイルの先頭を指定する
1:参照点を指定する
2:ファイルの終端を指定する

19行目は、参照点を先頭に移動しています。
20行目で、先頭から1バイト読み込んでデータを表示しています。

23行目で、参照点をファイルの終端 - 1バイトへ移動し、24行目で1バイト読んでで表示しています。

28行目は、参照点をファイルの中央に移動して、29行目でい1バイト読んで表示しています。

ランダムアクセスファイルの扱いはこんな感じです。

ファイルをバイナリーの更新モードでオープンして、seek + read (または write) のペアでアクセスするだけです。

ランダムアクセスを扱えるようになると、簡単なデータベースもどきも作成する事ができます。

いろいろと世界を広げていきましょう!

コメント

このブログの人気の投稿

Hyper-V で Docker Desktop for Windows を使う(その2)

Python の命名規約 - ネーミングルール

VS Code で Hyper-V + Docker Desktop for Windows