PythonでCSVを変換する、計算結果を保存する、設定ファイルを読む、CAD用データを出力する。こうした処理では、ファイルの内容だけでなく「どのファイルを、どの形式で、どの文字コードで開くか」を明確にする必要があります。
ファイル操作で起きやすい問題は次のとおりです。
- 実行位置が変わるとファイルが見つからない
- 読み取りと書き込みのモードを間違える
- 文字コードが一致せず文字化けや例外が起きる
- ファイルを閉じ忘れる
- 既存ファイルを意図せず上書きする
- テキストとバイナリを混同する
この記事では、with open()とpathlib.Pathを中心に、再利用しやすく環境へ依存しにくいファイル操作を学びます。
今日の到達点
open()のファイルモードを使い分けられるwithを使ってファイルを確実に閉じられる- UTF-8を明示してテキストを読み書きできる
- テキストモードとバイナリモードの違いを説明できる
pathlibでパスを組み立てられる- 相対パスと絶対パスの違いを理解できる
- ファイル不在や文字コード不一致を例外として処理できる
openでファイルを開く
Pythonのopen()はファイルオブジェクトを返します。基本形は次のとおりです。
file = open(
"report.txt",
mode="r",
encoding="utf-8",
)
主な引数は次の3つです。
- 第1引数:対象ファイルのパス
mode:読み取り、書き込みなどのモードencoding:テキストを文字とバイトの間で変換する方式
代表的なモードを整理します。
| モード | 用途 | ファイルがない場合 | 既存内容 |
|---|---|---|---|
r | 読み取り | エラー | 保持 |
w | 書き込み | 新規作成 | 消去して書き直す |
a | 追記 | 新規作成 | 末尾へ追加 |
x | 新規作成 | 新規作成 | 存在すればエラー |
b | バイナリ指定 | 他のモードと組み合わせる | バイト列を扱う |
たとえば"rb"はバイナリ読み取り、"wb"はバイナリ書き込みです。
withでファイルを確実に閉じる
ファイルは使い終わったら閉じる必要があります。with文を使うと、処理中に例外が発生してもブロックを抜ける際にファイルが閉じられます。
with open(
"report.txt",
mode="r",
encoding="utf-8",
) as file:
text = file.read()
print(text)
次のように手作業でclose()する方法もあります。
file = open("report.txt", encoding="utf-8")
text = file.read()
file.close()
しかし、read()とclose()の間で例外が起きると、close()へ到達しない可能性があります。通常のファイル操作ではwithを基本にします。
テキストファイルを読み込む
ファイル全体を文字列として読むにはread()を使います。
with open(
"pressure.txt",
mode="r",
encoding="utf-8",
) as file:
text = file.read()
print(text)
複数行を1行ずつ処理するなら、ファイルオブジェクトを直接反復できます。
with open(
"pressure.txt",
mode="r",
encoding="utf-8",
) as file:
for line_no, line in enumerate(file, start=1):
value = line.strip()
print(f"Line {line_no}: {value}")
strip()は行末の改行だけでなく前後の空白も除去します。空白自体に意味があるデータでは、必要に応じてrstrip("\r\n")などを使い、除去範囲を限定します。
大きなファイルはread()で全体をメモリへ読み込まず、1行ずつ処理する方法を検討します。
テキストファイルへ書き込む
wモードは、既存ファイルの内容を消去して先頭から書き込みます。
report = "Force: 14000.0 N\n"
with open(
"force_report.txt",
mode="w",
encoding="utf-8",
newline="\n",
) as file:
file.write(report)
write()は改行を自動追加しません。必要な位置へ\nを入れます。
複数行を書き込む例です。
lines = [
"component,force_n",
"Cylinder A,14000.0",
"Cylinder B,7000.0",
]
with open(
"force_report.txt",
mode="w",
encoding="utf-8",
newline="\n",
) as file:
file.write("\n".join(lines) + "\n")
出力内容です。
component,force_n
Cylinder A,14000.0
Cylinder B,7000.0
このコードはPython 3.12.14で実行確認済みです。
追記したい場合はaモードを使います。
with open(
"force_report.txt",
mode="a",
encoding="utf-8",
) as file:
file.write("Cylinder C,21000.0\n")
既存ファイルの上書きを禁止したい場合はxモードを使えます。
with open(
"new_report.txt",
mode="x",
encoding="utf-8",
) as file:
file.write("New report\n")
同名ファイルがすでに存在するとFileExistsErrorになります。
UTF-8を明示する
テキストモードでencodingを省略した場合、使用される文字コードは環境に依存します。同じコードでもOSや設定によって結果が変わる可能性があります。
公開するコードや複数環境で動かす処理では、文字コードを明示します。
with open("report.txt", encoding="utf-8") as file:
text = file.read()
書き込み時も同様です。
with open(
"report.txt",
mode="w",
encoding="utf-8",
) as file:
file.write("設定圧力は14 MPaです\n")
ただし、入力ファイルがUTF-8とは限りません。外部システムや古いソフトウェアが別の文字コードを使用している場合は、仕様を確認して正しいencodingを指定します。
文字化けを避けるために、根拠なくerrors="ignore"を指定してはいけません。読めなかった文字が消え、データ欠損に気づけなくなるためです。
テキストとバイナリの違い
テキストモードでは、ファイルのバイト列を指定した文字コードでstrへ変換します。
with open(
"report.txt",
mode="r",
encoding="utf-8",
) as file:
text = file.read()
print(type(text))
出力です。
<class 'str'>
バイナリモードでは変換せず、bytesとして扱います。
with open("sample.bin", mode="rb") as file:
data = file.read()
print(type(data))
出力です。
<class 'bytes'>
画像、PDF、圧縮ファイル、独自バイナリ形式などは通常バイナリとして扱います。バイナリモードではencodingを指定しません。
with open("sample.bin", mode="wb") as file:
file.write(b"\x00\x01\xff")
テキストファイルでも、文字コードの調査やハッシュ計算など、バイト単位の処理が必要ならバイナリモードを使う場合があります。
pathlibでパスを組み立てる
pathlibは、ファイルシステムのパスをオブジェクトとして扱う標準ライブラリです。
from pathlib import Path
output_dir = Path("output")
report_path = output_dir / "force_report.txt"
print(report_path)
/演算子でパス要素を連結できます。区切り文字を文字列として直接書く必要がありません。
ファイル名、拡張子、親ディレクトリも取得できます。
from pathlib import Path
report_path = Path("output") / "force_report.txt"
print(report_path.name)
print(report_path.stem)
print(report_path.suffix)
print(report_path.parent)
出力例です。
force_report.txt
force_report
.txt
output
ディレクトリを作成する例です。
from pathlib import Path
output_dir = Path("output")
output_dir.mkdir(parents=True, exist_ok=True)
parents=True:必要な親ディレクトリも作成するexist_ok=True:すでに存在してもエラーにしない
Pathでテキストとバイナリを読み書きする
短いファイルを一括で扱うなら、Path.read_text()とPath.write_text()も使えます。
from pathlib import Path
report_path = Path("output") / "force_report.txt"
report_path.parent.mkdir(
parents=True,
exist_ok=True,
)
report_path.write_text(
"Force: 14000.0 N\n",
encoding="utf-8",
newline="\n",
)
text = report_path.read_text(encoding="utf-8")
print(text, end="")
出力です。
Force: 14000.0 N
バイナリにはread_bytes()とwrite_bytes()があります。
from pathlib import Path
binary_path = Path("sample.bin")
binary_path.write_bytes(b"\x00\x01\xff")
data = binary_path.read_bytes()
print(data)
出力です。
b'\x00\x01\xff'
一括読み込みは簡潔ですが、大容量ファイルや逐次処理にはopen()を使います。
相対パスと絶対パス
相対パスは、基準となる場所から見た位置を表します。
from pathlib import Path
path = Path("data") / "pressure.txt"
絶対パスは、ファイルシステム上の位置を先頭から表します。
from pathlib import Path
path = Path("data") / "pressure.txt"
absolute_path = path.resolve()
print(absolute_path)
相対パスの基準は、通常「スクリプトが置かれた場所」ではなく、プロセスの現在の作業ディレクトリです。IDE、ターミナル、タスクスケジューラなど実行方法が変わると、基準位置も変わる可能性があります。
スクリプト自身と同じディレクトリを基準にする場合は、次のように明示できます。
from pathlib import Path
base_dir = Path(__file__).resolve().parent
data_path = base_dir / "data" / "pressure.txt"
対話環境などでは__file__が定義されない場合があるため、実行形態に合わせて基準を決めます。
公開記事へ、特定ユーザー名を含むPC固有の絶対パスを書かないようにします。環境ごとに変わる位置は、設定、コマンドライン引数、環境変数などから受け取る設計へ発展させられます。
ファイルが存在するか確認する
pathlibでは状態を確認できます。
from pathlib import Path
data_path = Path("data") / "pressure.txt"
print(data_path.exists())
print(data_path.is_file())
print(data_path.is_dir())
ただし、存在確認から実際に開くまでの間に、別の処理がファイルを削除する可能性があります。存在確認だけで安全を保証できるわけではありません。
処理上予想される失敗は、実際の読み込み時にも例外として扱います。
ファイルがない場合を処理する
存在しないファイルをrモードで開くとFileNotFoundErrorが発生します。
from pathlib import Path
data_path = Path("data") / "pressure.txt"
try:
text = data_path.read_text(encoding="utf-8")
except FileNotFoundError:
print(f"File not found: {data_path}")
else:
print(text)
入力必須のファイルがない場合、空データで処理を続けるより、対象パスを示して停止する方が安全な場合があります。
文字コードが違う場合を処理する
指定した文字コードで変換できないバイト列があると、UnicodeDecodeErrorが発生します。
from pathlib import Path
data_path = Path("data") / "pressure.txt"
try:
text = data_path.read_text(encoding="utf-8")
except FileNotFoundError:
print(f"File not found: {data_path}")
except UnicodeDecodeError as error:
print(f"Encoding error: {error}")
else:
print(text)
この場合は、ファイルの作成元や仕様を確認します。文字コードを推測して無条件に再試行すると、誤変換した文字列を正しいデータとして扱う危険があります。
実務例:計算結果を安全に保存して読む
出力ディレクトリを作成し、推力計算結果をUTF-8のテキストとして保存してから読み戻します。
from pathlib import Path
def build_force_report() -> str:
lines = [
"component,force_n",
"Cylinder A,14000.0",
"Cylinder B,7000.0",
]
return "\n".join(lines) + "\n"
def save_text(
output_path: Path,
text: str,
) -> None:
output_path.parent.mkdir(
parents=True,
exist_ok=True,
)
with output_path.open(
mode="w",
encoding="utf-8",
newline="\n",
) as file:
file.write(text)
def load_text(input_path: Path) -> str:
with input_path.open(
mode="r",
encoding="utf-8",
) as file:
return file.read()
report_path = Path("output") / "force_report.txt"
report = build_force_report()
save_text(report_path, report)
loaded_report = load_text(report_path)
print(loaded_report, end="")
実行結果です。
component,force_n
Cylinder A,14000.0
Cylinder B,7000.0
この例では処理を3つへ分けています。
build_force_report():出力内容を作るsave_text():パスへ保存するload_text():パスから読み込む
データ生成とファイルI/Oを分けると、保存せずに内容だけテストしたり、将来CSVやJSONへ出力方式を変更したりしやすくなります。
よくある失敗
wモードで既存ファイルを消す
wは既存内容を消去します。追記ならa、新規作成だけを許可するならxを検討します。重要ファイルを更新する場合は、一時ファイルへ書いてから置き換える設計も必要です。
encodingを省略する
環境の既定値へ依存すると、別のPCで再現しない可能性があります。扱う仕様がUTF-8ならencoding="utf-8"を明示します。
バックスラッシュを直接連結する
OS固有の区切り文字を手作業で連結すると移植性が下がります。Path("data") / "file.txt"のように組み立てます。
エラーを無視して空文字列を返す
入力ファイルの不在を空データとして扱うと、「本当にデータが0件」なのか「読み込みに失敗した」のか区別できません。原因を保持したまま停止するか、明示的な代替処理を選びます。
機密情報をパスやログへ出す
ローカル固有パスには、ユーザー名、案件名、顧客名などが含まれる場合があります。公開記事や共有ログには、一般化した相対パスだけを掲載します。
練習問題
UTF-8でテキストを保存する
result.txtへPressure: 14.0 MPaと改行を書き込んでください。
from pathlib import Path
result_path = Path("result.txt")
result_path.write_text(
"Pressure: 14.0 MPa\n",
encoding="utf-8",
newline="\n",
)
確認観点は、UTF-8と改行を明示していることです。
拡張子を取得する
data/measurement.csvからファイル名と拡張子を取得してください。
from pathlib import Path
data_path = Path("data") / "measurement.csv"
print(data_path.name)
print(data_path.suffix)
期待される出力です。
measurement.csv
.csv
ファイル不在を処理する
settings.txtが存在しない場合に、対象パスを含むメッセージを表示してください。
from pathlib import Path
settings_path = Path("settings.txt")
try:
settings = settings_path.read_text(
encoding="utf-8",
)
except FileNotFoundError:
print(f"File not found: {settings_path}")
else:
print(settings)
まとめ
open()はパス、モード、文字コードを指定して使う- ファイルは
withで開き、処理後に確実に閉じる rは読み取り、wは上書き、aは追記、xは新規作成である- テキストモードは
str、バイナリモードはbytesを扱う - UTF-8を使う処理では
encoding="utf-8"を明示する pathlib.PathでOS固有の区切り文字に依存せずパスを組み立てられる- 相対パスの基準は現在の作業ディレクトリである
FileNotFoundErrorとUnicodeDecodeErrorを原因別に扱う- 存在確認だけでは、実際の読み書きが成功する保証にならない
- データ生成とファイルI/Oを関数として分離すると再利用しやすい
ファイル操作は、単に読み書きできれば完了ではありません。パスの基準、文字コード、上書き規則、異常時の扱いを明示して、別の環境でも再現できる処理にすることが重要です。

