PythonにおけるファイルI/Oの基礎

ファイル操作の基本概念

オペレーティングシステムがファイルを扱う際、通常以下の手順を踏みます。

  1. ファイルを開き、ファイルディスクリプタ(またはファイルハンドル)を取得します。
  2. 取得したファイルディスクリプタを介して、ファイルに対する読み書き操作を行います。
  3. 操作が完了したら、ファイルディスクリプタを閉じます。

ファイルディスクリプタ(ファイルハンドル)とは、ファイルI/Oにおいて、開かれたファイルを一意に識別するための抽象的な参照です。アプリケーションがファイルからデータを読み書きする際、まずファイル名とパスを指定してオペレーティングシステムにファイルを「開く」よう要求します。このとき、OSから返されるのがファイルディスクリプタです。以降のデータの読み書きは、このディスクリプタを介して行われます。作業が完了したら、システムコールを通じてファイルを閉じることで、リソースを解放します。

ファイルの読み込み(Read Mode)

Pythonでファイルを読み込むには、組み込み関数open()を使用します。読み込みモードは'r'です。ファイルのエンコーディングを指定することは非常に重要です。

1. ファイル全体を読み込む: read()

ファイル全体の内容を一つの文字列として読み込みます。

# sample.txt の内容を想定:
# Pythonは
# 素晴らしい
# プログラミング言語です。

file_path = 'sample.txt'
try:
    with open(file_path, 'r', encoding='utf-8') as file_obj:
        content = file_obj.read()
        print("ファイルの内容:")
        print(content)
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

ファイルの内容:
Pythonは
素晴らしい
プログラミング言語です。

2. 指定したバイト数/文字数を読み込む: read(n)

引数nに指定された数だけ、ファイルから読み込みます。テキストモード('r')では文字数、バイナリモード('rb')ではバイト数として扱われます。

テキストモード('r')での読み込み

指定した文字数だけ読み込みます。

# data.txt の内容を想定: 「こんにちは、世界!」 (UTF-8エンコード)

file_path = 'data.txt'
try:
    with open(file_path, 'r', encoding='utf-8') as file_obj:
        print(f"最初の3文字: {file_obj.read(3)}") # 「こんにち」を読み込む
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例: 最初の3文字: こんにちは

バイナリモード('rb')での読み込み

指定したバイト数だけ読み込みます。読み込んだバイト列は.decode()でデコードできます。

# data.txt の内容を想定: 「こんにちは、世界!」 (UTF-8エンコード)
# UTF-8で「こ」は3バイト、「ん」は3バイト、「に」は3バイト。

file_path = 'data.txt'
try:
    with open(file_path, 'rb') as file_obj:
        binary_data = file_obj.read(3) # 最初の3バイトを読み込む
        print(f"最初の3バイト (バイナリ): {binary_data}")
        print(f"デコード後: {binary_data.decode('utf-8')}") # 「こ」だけになる
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

最初の3バイト (バイナリ): b'\xe3\x81\x93'
デコード後: こ

3. 1行ずつ読み込む: readline()

ファイルを1行ずつ読み込みます。行末の改行文字(\n)も含まれます。

# multi_line.txt の内容を想定:
# 最初の行です
# 二行目です
# 最後の行

file_path = 'multi_line.txt'
try:
    with open(file_path, 'r', encoding='utf-8') as file_obj:
        first_line = file_obj.readline()
        second_line = file_obj.readline()
        print(f"1行目: {first_line.strip()}") # .strip()で改行文字を除去
        print(f"2行目: {second_line.strip()}")
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

1行目: 最初の行です
2行目: 二行目です

4. 全ての行をリストで読み込む: readlines()

ファイルの全ての行を読み込み、各行を要素とするリストとして返します。

# multi_line.txt の内容を想定:
# 最初の行です
# 二行目です
# 最後の行

file_path = 'multi_line.txt'
try:
    with open(file_path, 'r', encoding='utf-8') as file_obj:
        all_lines = file_obj.readlines()
        print("全行リスト:")
        print(all_lines)
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

全行リスト:
['最初の行です\n', '二行目です\n', '最後の行']

5. forループで逐次読み込む

ファイルをイテレータとして扱うことで、メモリ効率良く1行ずつ処理できます。大きなファイルを扱う際に推奨される方法です。

# multi_line.txt の内容を想定:
# 最初の行です
# 二行目です
# 最後の行

file_path = 'multi_line.txt'
print("forループで読み込み:")
try:
    with open(file_path, 'r', encoding='utf-8') as file_obj:
        for index, line in enumerate(file_obj):
            print(f"行 {index + 1}: {line.strip()}")
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

forループで読み込み:
行 1: 最初の行です
行 2: 二行目です
行 3: 最後の行

6. 読み書きモード: 'r+'(読み込み後に書き込み)

このモードでは、ファイルを読み書き両用で開きます。ファイルポインタはファイルの先頭に置かれます。

# modify_me.txt の初期内容を想定:
# Original text line 1.
# Original text line 2.

file_path = 'modify_me.txt'

# 初期ファイル作成(デモンストレーション用)
with open(file_path, 'w', encoding='utf-8') as f:
    f.write('Original text line 1.\n')
    f.write('Original text line 2.\n')

print("--- r+ モードのデモンストレーション ---")
try:
    with open(file_path, 'r+', encoding='utf-8') as file_obj:
        print("変更前の内容:")
        print(file_obj.read()) # 全体を読み込むと、ファイルポインタは末尾へ移動

        # ファイルポインタを先頭に戻す
        file_obj.seek(0)
        file_obj.write('新しいテキストを先頭に追加。') # 既存のテキストを上書き

        # ファイルポインタを再度先頭に戻して、変更後の内容を確認
        file_obj.seek(0)
        print("\n変更後の内容:")
        print(file_obj.read())

except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

--- r+ モードのデモンストレーション ---
変更前の内容:
Original text line 1.
Original text line 2.

変更後の内容:
新しいテキストを先頭に追加。line 1.
Original text line 2.

注意: Windows環境でファイルのパスを指定する場合、バックスラッシュ(\)はエスケープ文字として扱われるため、rプレフィックスを付けて生の文字列(Raw String)として扱うか、スラッシュ(/)を使用するか、バックスラッシュを二重にする(\\)のが一般的です。例: open(r'D:\my_folder\my_file.txt', ...) または open('D:/my_folder/my_file.txt', ...)

ファイルの書き込み(Write Mode)

1. 書き込みモード: 'w'

ファイルを書き込み専用で開きます。ファイルが存在しない場合は新規作成され、既に存在する場合はその内容が完全に消去されます。

file_path = 'output.txt'

# ファイルに新しい内容を書き込む
with open(file_path, 'w', encoding='utf-8') as file_obj:
    file_obj.write('これは新しいファイルの内容です。\n')
    file_obj.write('以前のデータは消去されました。')

print(f"ファイル '{file_path}' に書き込みました。")

# 書き込まれた内容を確認(念のため読み込みモードで開く)
print("\nファイル内容の確認:")
with open(file_path, 'r', encoding='utf-8') as file_obj:
    print(file_obj.read())

出力例:

ファイル 'output.txt' に書き込みました。

ファイル内容の確認:
これは新しいファイルの内容です。
以前のデータは消去されました。

2. バイナリファイルのコピー

画像ファイルなどのバイナリデータを扱う場合、バイナリモード('rb'で読み込み、'wb'で書き込み)を使用します。これにより、データが変更されずにそのままコピーされます。

# dummy.jpg という既存の画像ファイルを想定
source_image_path = 'dummy.jpg'
destination_image_path = 'dummy_copy.jpg'

try:
    with open(source_image_path, 'rb') as source_file:
        binary_content = source_file.read()

    with open(destination_image_path, 'wb') as destination_file:
        destination_file.write(binary_content)

    print(f"画像ファイル '{source_image_path}' を '{destination_image_path}' にコピーしました。")
except FileNotFoundError:
    print(f"エラー: 元の画像ファイル '{source_image_path}' が見つかりません。")

# このコードを実行すると、dummy.jpg が dummy_copy.jpg として複製されます。
# binary_content はバイト列 (b'...') として表示されます。

3. 書き込み読み込みモード: 'w+'(書き込み後に読み込み)

ファイルを読み書き両用で開きます。'w'モードと同様に、ファイルが存在する場合は内容が消去され、ファイルポインタは先頭に置かれます。

file_path = 'write_read_test.txt'

print("--- w+ モードのデモンストレーション ---")
with open(file_path, 'w+', encoding='utf-8') as file_obj:
    file_obj.write('w+モードで書き込んだ内容です。\n')
    file_obj.write('これは二行目。')

    # 書き込み後、ファイルポインタは末尾にあるため、読み込むには先頭に戻す必要がある
    file_obj.seek(0)
    read_content = file_obj.read()
    print("\n書き込み後に読み込んだ内容:")
    print(read_content)

出力例:

--- w+ モードのデモンストレーション ---

書き込み後に読み込んだ内容:
w+モードで書き込んだ内容です。
これは二行目。

4. 追記モード: 'a'

ファイルを追記専用で開きます。ファイルが存在しない場合は新規作成され、存在する場合はファイルの末尾に新しい内容が追加されます。このモードでは読み込みはできません。

file_path = 'append_log.txt'

# 初回書き込み(ファイル作成)
with open(file_path, 'w', encoding='utf-8') as f:
    f.write('最初のログエントリ。\n')

print("--- a モードのデモンストレーション ---")
with open(file_path, 'a', encoding='utf-8') as file_obj:
    file_obj.write('追加されたログエントリ。\n')
    file_obj.write('もう一つの追加エントリ。\n')

print(f"ファイル '{file_path}' に追記しました。")

# ファイルの内容を確認
print("\nファイル内容の確認:")
with open(file_path, 'r', encoding='utf-8') as file_obj:
    print(file_obj.read())

出力例:

--- a モードのデモンストレーション ---
ファイル 'append_log.txt' に追記しました。

ファイル内容の確認:
最初のログエントリ。
追加されたログエントリ。
もう一つの追加エントリ。

5. 追記読み書きモード: 'a+'

ファイルを追記および読み書き両用で開きます。ファイルが存在しない場合は新規作成され、存在する場合はファイルポインタが末尾に置かれます。読み込みを行う場合は、seek()でファイルポインタを移動させる必要があります。

file_path = 'append_read_test.txt'

# 初期ファイル作成(デモンストレーション用)
with open(file_path, 'w', encoding='utf-8') as f:
    f.write('既存のテキスト。\n')

print("--- a+ モードのデモンストレーション ---")
with open(file_path, 'a+', encoding='utf-8') as file_obj:
    # 現在のファイルポインタ位置(末尾)に追記
    file_obj.write('a+モードで追加された新しい内容。\n')

    # 読み込むためにファイルポインタを先頭に戻す
    file_obj.seek(0)
    read_content = file_obj.read()
    print("\nファイルの内容(追記後に読み込み):")
    print(read_content)

出力例:

--- a+ モードのデモンストレーション ---

ファイルの内容(追記後に読み込み):
既存のテキスト。
a+モードで追加された新しい内容。

その他のファイル操作メソッド

ファイルオブジェクトには、I/O操作を制御するための便利なメソッドがいくつかあります。

  • file_obj.readable(): ファイルが読み込み可能ならTrueを返します。
  • file_obj.writable(): ファイルが書き込み可能ならTrueを返します。
  • file_obj.seek(offset, whence=0): ファイルポインタの位置を変更します。
    • whence=0(デフォルト): ファイルの先頭からのオフセット。
    • whence=1: 現在位置からのオフセット。
    • whence=2: ファイルの末尾からのオフセット。
  • file_obj.tell(): 現在のファイルポインタの位置(バイト数)を返します。
  • file_obj.truncate(size=None): ファイルを現在のファイルポインタ位置、またはsizeで指定されたバイト数で切り詰めます。注意: このメソッドは'r+'、'w+'、'a+'など、書き込み可能なモードで開かれたファイルに対してのみ有効で、ファイルポインタの位置から後方を削除します。

with open()ステートメントによるリソース管理

Pythonでは、with open(...) as file_object:構文を使用することが推奨されます。これにより、ファイルのオープンとクローズが自動的に処理され、ファイルオブジェクトを明示的にclose()する必要がなくなります。これはエラー発生時でもリソースが確実に解放されるため、非常に安全な方法です。

単一ファイルの操作

# data_for_with.txt の内容を想定:
# 最初のデータ行
# 次のデータ行

file_path = 'data_for_with.txt'
try:
    with open(file_path, 'r', encoding='utf-8') as data_file:
        content = data_file.read()
        print("with ステートメントで読み込んだ内容:")
        print(content)
except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")

出力例:

with ステートメントで読み込んだ内容:
最初のデータ行
次のデータ行

複数ファイルの同時操作

withステートメントは、カンマ区切りで複数のファイルを同時に開くこともできます。

# file_a.txt の内容を想定: "Hello from File A!"
# file_b.txt の内容を想定: "Initial content for File B.\n"

file_a_path = 'file_a.txt'
file_b_path = 'file_b.txt'

# 初期ファイル作成(デモンストレーション用)
with open(file_a_path, 'w', encoding='utf-8') as f:
    f.write('Hello from File A!')
with open(file_b_path, 'w', encoding='utf-8') as f:
    f.write('Initial content for File B.\n')


print("--- 複数ファイルのwithステートメントデモンストレーション ---")
try:
    with open(file_a_path, 'r', encoding='utf-8') as read_file, \
         open(file_b_path, 'a+', encoding='utf-8') as append_read_file:

        # file_a から読み込み
        content_a = read_file.read()
        print(f"'{file_a_path}' の内容: {content_a}")

        # file_b に追記
        append_read_file.write('新しいテキストを追記しました。\n')
        
        # file_b を読み込むためにポインタを先頭に戻す
        append_read_file.seek(0)
        content_b = append_read_file.read()
        print(f"'{file_b_path}' の内容 (追記後): \n{content_b}")

except FileNotFoundError as e:
    print(f"エラー: ファイルが見つかりません - {e}")

出力例:

--- 複数ファイルのwithステートメントデモンストレーション ---
'file_a.txt' の内容: Hello from File A!
'file_b.txt' の内容 (追記後): 
Initial content for File B.
新しいテキストを追記しました。

ファイルの変更(インプレース編集のパターン)

既存のファイルを直接「変更」するモードは存在しません。そのため、ファイルを変更する一般的なパターンは、「一時ファイルを作成し、元のファイルを置き換える」というものです。

  1. 元のファイルを読み込みモードで開きます。
  2. 一時ファイルを書き込みモードで開きます。
  3. 元のファイルの内容を読み込み、必要な変更を加えて一時ファイルに書き込みます。
  4. 元のファイルを削除します。
  5. 一時ファイルを元のファイル名にリネームします。

1. ファイル全体を読み込んで変更する

import os

source_file_path = 'my_document.txt'
temp_file_path = 'my_document.txt.tmp'

# 初期ファイル作成(デモンストレーション用)
with open(source_file_path, 'w', encoding='utf-8') as f:
    f.write('ABCDEF\n')
    f.write('123abc456\n')
    f.write('XYZABC\n')

print("--- ファイル全体を読み込んで変更 ---")
print(f"変更前の '{source_file_path}' の内容:")
with open(source_file_path, 'r', encoding='utf-8') as f:
    print(f.read())

try:
    with open(source_file_path, 'r', encoding='utf-8') as input_file, \
         open(temp_file_path, 'w', encoding='utf-8') as output_file:
        
        original_content = input_file.read()
        modified_content = original_content.replace('ABC', 'XYZ') # 'ABC' を 'XYZ' に置換
        output_file.write(modified_content)

    os.remove(source_file_path) # 元のファイルを削除
    os.rename(temp_file_path, source_file_path) # 一時ファイルを元の名前に変更

    print(f"\n変更後の '{source_file_path}' の内容:")
    with open(source_file_path, 'r', encoding='utf-8') as f:
        print(f.read())

except FileNotFoundError:
    print(f"エラー: ファイル '{source_file_path}' が見つかりません。")

出力例:

--- ファイル全体を読み込んで変更 ---
変更前の 'my_document.txt' の内容:
ABCDEF
123abc456
XYZABC

変更後の 'my_document.txt' の内容:
XYZDEF
123xyz456
XYZXYZ

2. 行ごとに読み込んで変更する(大規模ファイル向け)

ファイル全体を一度にメモリに読み込むと、大規模なファイルではメモリ不足になる可能性があります。その場合、行ごとに読み込み・変更・書き込みを行う方法が効率的です。

import os

source_file_path = 'large_document.txt'
temp_file_path = 'large_document.txt.tmp'

# 初期ファイル作成(デモンストレーション用)
with open(source_file_path, 'w', encoding='utf-8') as f:
    f.write('Item 1: Status Pending\n')
    f.write('Item 2: Status Completed\n')
    f.write('Item 3: Status Pending\n')
    f.write('Item 4: Status Completed\n')

print("--- 行ごとに読み込んで変更 ---")
print(f"変更前の '{source_file_path}' の内容:")
with open(source_file_path, 'r', encoding='utf-8') as f:
    print(f.read())

try:
    with open(source_file_path, 'r', encoding='utf-8') as input_file, \
         open(temp_file_path, 'w', encoding='utf-8') as output_file:
        
        for line in input_file:
            modified_line = line.replace('Pending', 'Processed') # 'Pending' を 'Processed' に置換
            output_file.write(modified_line)

    os.remove(source_file_path) # 元のファイルを削除
    os.rename(temp_file_path, source_file_path) # 一時ファイルを元の名前に変更

    print(f"\n変更後の '{source_file_path}' の内容:")
    with open(source_file_path, 'r', encoding='utf-8') as f:
        print(f.read())

except FileNotFoundError:
    print(f"エラー: ファイル '{source_file_path}' が見つかりません。")

出力例:

--- 行ごとに読み込んで変更 ---
変更前の 'large_document.txt' の内容:
Item 1: Status Pending
Item 2: Status Completed
Item 3: Status Pending
Item 4: Status Completed

変更後の 'large_document.txt' の内容:
Item 1: Status Processed
Item 2: Status Completed
Item 3: Status Processed
Item 4: Status Completed

タグ: Python ファイルI/O テキストファイル バイナリファイル ファイルモード

11月3日 00:29 投稿