Pythonで「処理中だけ使うファイル」や「一時的な作業ディレクトリ」を作りたいときは、標準ライブラリの tempfile を使います。
** tempfile は標準ライブラリなので、追加インストールなしで使えます。**
import tempfile
この記事では、 tempfile でよく使う操作をチートシートとしてまとめます。
先に使い分けをまとめると、次のようになります。
| 状況 | 使うもの |
|---|---|
| ファイル名を外部に渡さず、一時ファイルとして読み書きしたい | TemporaryFile() |
| 外部コマンドやライブラリにファイルパスを渡したい | NamedTemporaryFile() |
| 複数ファイルを作る作業場所が欲しい | TemporaryDirectory() |
| 小さい間はメモリ、大きくなったらディスクに逃がしたい | SpooledTemporaryFile() |
| 文字列/バイト列をメモリ上だけで扱いたい |
io.StringIO / io.BytesIO
|
tempfile が使う一時ディレクトリを確認したい |
gettempdir() |
tempfileの何がうれしいのか
** tempfile の一番の利点は、一時ファイルや一時ディレクトリを安全に作り、後片付けまで任せやすいことです。**
具体的には、次の3点がうれしいところだと感じています。
| メリット | 何がうれしいか |
|---|---|
with 文で後片付けできる |
一時ファイルや一時ディレクトリのクローズ、削除漏れを減らせる |
SpooledTemporaryFile() がある |
小さい間はメモリ、大きくなったらディスクに逃がせるため、大容量バッファでもメモリを圧迫しにくい |
gettempdir() がある |
OSや実行環境ごとに異なる一時ディレクトリの場所を吸収してくれる |
たとえば、適当なファイル名を自分で作ると、名前の衝突や削除漏れを考える必要があります。
from pathlib import Path
path = Path("tmp.txt")
path.write_text("hello\n", encoding="utf-8")
# 処理を書く
path.unlink()
tempfile を使うと、一時ファイル名の生成や削除を任せられます。
from tempfile import NamedTemporaryFile
with NamedTemporaryFile(mode="w+t", encoding="utf-8") as f:
f.write("hello\n")
f.seek(0)
print(f.read())
まずは with 文で使える高水準APIを選ぶのが基本です。
TemporaryFile() 、 NamedTemporaryFile() 、 TemporaryDirectory() 、 SpooledTemporaryFile() は、コンテキストマネージャとして使えます。
with ブロックを抜けると、閉じる処理や削除処理を任せられます。これにより、例外が発生した場合でも一時ファイルや一時ディレクトリの後片付けを書き忘れにくくなります。
まず覚える基本形
| やりたいこと | 使うもの | ポイント |
|---|---|---|
| 名前を気にせず一時ファイルを使う | TemporaryFile() |
ファイルパスを外部に渡さない場合に使う |
| パス付きの一時ファイルを使う | NamedTemporaryFile() |
外部コマンドやライブラリにファイルパスを渡したい場合に使う |
| 一時ディレクトリを作る | TemporaryDirectory() |
複数ファイルをまとめて扱う作業場所に使う |
| 小さい間はメモリ、大きくなったらファイルに逃がす | SpooledTemporaryFile() |
サイズが読みにくい一時データに使う |
| 一時ディレクトリの場所を確認する | gettempdir() |
tempfile が使うデフォルトの一時ディレクトリを確認する |
低水準APIとして mkstemp() と mkdtemp() もありますが、普段は with 文で扱える高水準APIを先に選ぶ方が簡潔です。1
名前を気にせず一時ファイルを使う
ファイル名を使わないなら、 TemporaryFile() が素直です。
from tempfile import TemporaryFile
with TemporaryFile(mode="w+t", encoding="utf-8") as f:
f.write("hello\n")
f.seek(0)
text = f.read()
print(text)
mode="w+t" は、テキストモードで読み書きする指定です。
TemporaryFile() のデフォルトは mode="w+b" です。
バイナリデータを扱う場合はデフォルトのままでよいですが、文字列を書きたい場合は mode="w+t" と encoding を指定すると分かりやすいです。
パス付きの一時ファイルを使う
外部コマンドやライブラリにファイルパスを渡したい場合は、 NamedTemporaryFile() を使います。
from pathlib import Path
from tempfile import NamedTemporaryFile
with NamedTemporaryFile(
mode="w+t",
encoding="utf-8",
suffix=".txt",
prefix="sample-",
) as f:
path = Path(f.name)
f.write("hello\n")
f.flush()
print(path)
NamedTemporaryFile() では、作成された一時ファイルのパスを name 属性から取得できます。
suffix や prefix を指定すると、拡張子や接頭辞が必要なツールにも渡しやすくなります。
** NamedTemporaryFile() を別プロセスや別の open() で開き直す場合は、削除タイミングに注意が必要です。**
特にWindowsでは、開いたままの一時ファイルを別途開き直すと、削除時に PermissionError になることがあります。
デバッグなどでファイルを残したい場合は delete=False を使えますが、その場合は自分で削除する必要があります。
一時ディレクトリを作る
複数のファイルをまとめて扱うなら、 TemporaryDirectory() が便利です。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmpdir:
work_dir = Path(tmpdir)
input_path = work_dir / "input.txt"
output_path = work_dir / "output.txt"
input_path.write_text("hello\n", encoding="utf-8")
output_path.write_text(input_path.read_text(encoding="utf-8").upper(), encoding="utf-8")
print(output_path.read_text(encoding="utf-8"))
with ブロックを抜けると、一時ディレクトリと中身のファイルは削除されます。
テスト、変換処理、アーカイブ作成、外部コマンドの作業場所などで使いやすいです。
小さい間はメモリ、大きくなったらファイルにする
SpooledTemporaryFile() は、一定サイズまではメモリ上に保持し、サイズが大きくなるとディスク上の一時ファイルに切り替えます。
大きくなる可能性がある一時データを、最初からすべてメモリに抱え込まなくてよいのが利点です。
メモリ上でファイルのように扱うだけなら、 io.StringIO や io.BytesIO も選択肢になります。
| 使うもの | 向いている場面 |
|---|---|
io.StringIO |
文字列をメモリ上でファイルのように扱いたい |
io.BytesIO |
バイト列をメモリ上でファイルのように扱いたい |
SpooledTemporaryFile() |
小さい間はメモリで扱い、大きくなったらディスクへ逃がしたい |
** io.StringIO と io.BytesIO は完全にメモリ上のバッファです。**
シンプルで扱いやすい一方、データが大きくなっても自動でディスクには切り替わりません。サイズが読みにくい一時データでは、 SpooledTemporaryFile() の方が安心して使いやすいです。
from tempfile import SpooledTemporaryFile
with SpooledTemporaryFile(max_size=1024, mode="w+t", encoding="utf-8") as f:
f.write("hello\n")
f.seek(0)
print(f.read())
普段は小さいが、ときどき大きくなる一時データを扱うときに便利です。
SpooledTemporaryFile() は、データサイズが max_size を超えたとき、または fileno() が呼ばれたときにディスク上のファイルへ切り替わります。
明示的に切り替えたい場合は rollover() を呼びます。
ファイル名や作成場所を指定する
tempfile の各APIでは、必要に応じて suffix 、 prefix 、 dir を指定できます。
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory
with TemporaryDirectory() as tmpdir:
work_dir = Path(tmpdir)
with NamedTemporaryFile(
mode="w+t",
encoding="utf-8",
suffix=".csv",
prefix="report-",
dir=work_dir,
) as f:
f.write("id,name\n1,Alice\n")
f.flush()
print(Path(f.name))
| 引数 | 意味 | 例 |
|---|---|---|
suffix |
ファイル名の末尾 |
".csv" 、 ".json"
|
prefix |
ファイル名の先頭 |
"report-" 、 "work-"
|
dir |
作成先ディレクトリ |
Path("tmp") 、 work_dir
|
** suffix にドットは自動では付きません。**
CSVファイルのように拡張子を付けたい場合は、 suffix=".csv" のようにドット込みで指定します。
tempfile の引数順は後方互換性の都合で少し分かりにくいため、 suffix 、 prefix 、 dir などはキーワード引数で指定するのがおすすめです。
一時ディレクトリの場所を確認する
gettempdir() で、 tempfile が使うデフォルトの一時ディレクトリを確認できます。
import tempfile
print(tempfile.gettempdir())
デフォルトの一時ディレクトリは、環境変数 TMPDIR 、 TEMP 、 TMP や、OSごとの標準的な一時ディレクトリから選ばれます。
Linux、macOS、Windows、コンテナ、CIなどで一時ディレクトリの場所が違っても、 tempfile 側でその差を吸収してくれるのが便利です。
一時ファイルの作成場所を変えたい場合は、 tempfile.tempdir を書き換えるよりも dir= を指定する方がよいです。
tempfile.tempdir はグローバルな設定なので、他の処理にも影響します。
よく使う場面
tempfile は、次のような場面で使いやすいです。
| 場面 | 使うもの |
|---|---|
| テスト中だけファイルを作りたい | TemporaryDirectory() |
| 外部コマンドに一時ファイルのパスを渡したい | NamedTemporaryFile() |
| PDF、画像、CSVなどの中間ファイルを作りたい |
NamedTemporaryFile() / TemporaryDirectory()
|
| 複数ファイルをまとめて一時的に扱いたい | TemporaryDirectory() |
| 一時データが小さいことが多いが、大きくなる可能性もある | SpooledTemporaryFile() |
まとめ
普段使いでは、まず次の3つを覚えておけば十分です。
| やりたいこと | 使うもの |
|---|---|
| ファイル名が不要な一時ファイル | TemporaryFile() |
| ファイルパスが必要な一時ファイル | NamedTemporaryFile() |
| 一時的な作業ディレクトリ | TemporaryDirectory() |
一時ファイルや一時ディレクトリは、作ることよりも後片付けまで含めて安全に扱うことが重要です。
その意味で、 tempfile は「一時的な置き場所」を雑に作らないための標準的な道具として使えます。
参考
-
mkstemp()はOSレベルのファイルディスクリプタとパスを返し、mkdtemp()は一時ディレクトリのパスを返します。どちらも作成自体は安全ですが、使い終わった後の削除は自分で書く必要があります。OSレベルのファイルディスクリプタが必要な場合などを除けば、まずはwith文で扱える高水準APIを使う方が楽です。 ↩