プログラムを書いていると、ほんの少しのあいだだけファイルが必要になる場面があります。ダウンロードしたデータをいったん保存したり、別のツールに渡す入力ファイルを用意したりするときです。
そんなとき、作業フォルダにtmp.txtのような名前でファイルを作っていませんか。動くには動くのですが、あとで消し忘れたり、同時に動いた別の処理と名前がぶつかったりと、小さな事故の種になりがちです。
Pythonには、この一時的なファイルを安全に扱うためのtempfileという標準ライブラリがあります。今回は、tempfileの基本から、実務でよく使う関数の使い分け、つまずきやすい注意点までを順番に見ていきましょう。
tempfileは、使い捨てのファイルを安全に用意する道具¶
tempfileは、一時ファイルと一時フォルダを作るためのモジュールです。Pythonに最初から入っているので、インストールは不要で、importするだけで使えます。
一時ファイルとは、処理が終わったら用済みになるファイルのことです。英語のtemporaryを略してtempと呼ばれることが多く、モジュール名もここから来ています。
自分で名前を付けて作ると何が困るのか¶
一時ファイルを自分で作ると、困りごとが大きく3つあります。どれも、書いた直後には気づきにくいのがやっかいなところです。
1つ目は名前の衝突です。同じスクリプトを2つ同時に動かすと、どちらもtmp.txtに書き込み、中身が混ざってしまいます。
2つ目は消し忘れです。途中で例外が起きると後片付けのコードまでたどり着かず、ゴミのファイルが少しずつたまっていきます。
3つ目は安全性です。ほかのユーザーも使う共有のフォルダでは、推測しやすい名前のファイルを先回りして作られる攻撃が知られています。
私も駆け出しのころ、夜間バッチでこの名前の衝突を起こしたことがあります。2つのジョブが同じ一時ファイルを上書きし合い、翌朝に集計結果が合わない原因を半日かけて探しました。
tempfileが代わりにやってくれること¶
tempfileを使うと、この3つの困りごとをまとめて解決できます。何をしてくれるのかを、表で整理しておきます。
| 困りごと | tempfileの対策 |
|---|---|
| 名前の衝突 | ランダムな文字を含む、重ならない名前を自動で付ける |
| 消し忘れ | with文を抜けたときや、ファイルを閉じたときに自動で削除する |
| 安全性 | 作成したユーザーだけが読み書きできる形で、競合しないようにファイルを作る |
| 置き場所 | OSごとに決められた一時フォルダを自動で探して使う |
特に大きいのは、後片付けをPythonに任せられることです。途中でエラーが起きても、with文と組み合わせればきちんと消してくれます。
まずはTemporaryFileで一時ファイルを作ってみよう¶
いちばんシンプルなのが、TemporaryFileという関数です。名前を気にせず、書いて読んで捨てるだけの用途に向いています。
さっそく、文字を書き込んで読み戻すコードを動かしてみましょう。
import tempfile
with tempfile.TemporaryFile(mode="w+", encoding="utf-8") as f:
f.write("一時的なメモです\n")
f.seek(0) # 読み込み位置を先頭に戻す
print(f.read()) # 一時的なメモです
# withを抜けた時点で、ファイルはもう消えている
使い方は、ふつうのopen関数とほとんど同じです。with文を抜けるとファイルが閉じられ、その時点で自動的に削除されます。
open関数とwith文の関係があいまいな方は、先に基本を押さえておくと理解が早くなります。【関連記事】Pythonのファイル読み書きとは?open()とwith文の基本を初心者向けに解説
modeの初期値はバイナリなので注意¶
ここでひとつ、初心者がつまずきやすいポイントがあります。TemporaryFileのmodeの初期値は、w+bというバイナリモードです。
そのため、modeを指定せずに文字列を書き込もうとすると、TypeErrorになります。文字を扱うときは、mode="w+"とencodingを明示する癖をつけておくと安心です。
また、書き込んだ直後に読もうとしても、読み込み位置がファイルの末尾にあるので何も返ってきません。上のコードでseek(0)を呼んでいるのは、位置を先頭へ戻すためです。
名前が必要ならNamedTemporaryFileを使う¶
TemporaryFileで作ったファイルは、名前を使って別の場所から開く用途には向いていません。Linuxなどでは、作った直後にファイル名がフォルダから消されることもあるからです。
外部のコマンドやライブラリにファイルのパスを渡したいときは、NamedTemporaryFileを使います。こちらはname属性で、ファイルのフルパスを取り出せます。
Python 3.12で加わったdelete_on_close¶
NamedTemporaryFileには長いあいだ、少し困った性質がありました。ファイルを閉じると同時に削除されるため、いったん閉じてから別の処理に開き直してもらう、という使い方がしにくかったのです。
Python 3.12では、この問題を解決するdelete_on_closeという引数が加わりました。Falseを指定すると、閉じても消さずに、with文を抜けたときに削除してくれます。
実際に、CSVを一時ファイルに書き出してから、パスを使って読み直す例を見てみましょう。
import csv
import os
import tempfile
rows = [["name", "score"], ["sato", 80], ["suzuki", 92]]
with tempfile.NamedTemporaryFile(
mode="w", suffix=".csv", encoding="utf-8",
newline="", delete_on_close=False,
) as f:
csv.writer(f).writerows(rows)
f.close() # いったん閉じても、まだ消えない
print(f.name) # 例: /tmp/tmpk3x9a1b2.csv
with open(f.name, encoding="utf-8") as reader:
print(reader.read())
print(os.path.exists(f.name)) # False(withを抜けたら削除済み)
suffixを指定すると、ファイル名の末尾に拡張子を付けられます。拡張子で形式を判断するツールに渡すときに便利です。
同じようにprefixを指定すれば、ファイル名の先頭に目印の文字を付けられます。あとで一時フォルダをのぞいたときに、どのプログラムが作ったファイルかすぐにわかります。
3.11以前ではdelete=Falseと手動削除を組み合わせる¶
delete_on_closeは3.12からの機能なので、それより古いPythonでは使えません。古い環境では、delete=Falseを指定して自動削除を止め、最後に自分で消すのが定番でした。
この書き方は、消す処理をtry文のfinallyに書かないと消し忘れが起きます。実行環境が3.12以上なら、delete_on_closeを使うほうが安全でシンプルです。
自分の環境のバージョンがわからないときは、ターミナルでpython --versionを実行して確かめておきましょう。
一時フォルダが欲しいときはTemporaryDirectory¶
ファイル1つでは足りず、作業用のフォルダごと欲しい場面もあります。たとえば、zipを展開して中身を加工したり、複数の中間ファイルを並べて作ったりするときです。
そんなときに使うのがTemporaryDirectoryです。with文と組み合わせると、フォルダの中身ごとまとめて削除してくれます。
pathlibと組み合わせた例を見てみましょう。
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory(prefix="report_") as tmp:
work = Path(tmp)
(work / "a.txt").write_text("1つ目", encoding="utf-8")
(work / "b.txt").write_text("2つ目", encoding="utf-8")
for path in sorted(work.iterdir()):
print(path.name, path.read_text(encoding="utf-8"))
# withを抜けると、フォルダも中のファイルもまとめて消える
with文のasで受け取れるのは、フォルダのパスを表す文字列です。Pathに変換しておくと、スラッシュ記号でパスをつなげられて読みやすくなります。
pathlibの書き方にまだ慣れていない方は、こちらの記事もあわせてどうぞ。【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説
できあがった成果物だけを残したいときは、with文を抜ける前に、正式な保存先へコピーしておきます。ファイルのコピーや移動にはshutilが便利です。
【関連記事】Pythonのshutilとは?ファイルのコピーや移動、zip圧縮を自動化する基本を初心者向けに解説
mkstempとmkdtempは、自分で片付ける前提の関数¶
tempfileには、自動削除をしない低レベルな関数もあります。mkstempとmkdtempです。
どちらも名前の重ならないファイルやフォルダを安全に作ってくれますが、削除は呼び出した側の責任になります。後片付けまで面倒を見てくれるのは、ここまで紹介した3つの関数だけだと覚えておきましょう。
ここまでの関数を、特徴ごとに比べてみます。
| 関数 | 作るもの | 名前でアクセス | 自動削除 |
|---|---|---|---|
| TemporaryFile | ファイル | 前提にしない | 閉じたとき |
| NamedTemporaryFile | ファイル | できる | 閉じたとき、またはwithを抜けたとき |
| TemporaryDirectory | フォルダ | できる | withを抜けたとき |
| mkstemp | ファイル | できる | しない |
| mkdtemp | フォルダ | できる | しない |
mkstempは少し独特で、ファイルのパスだけでなく、OSレベルのファイル番号も返します。この番号を閉じ忘れると、ファイルを開きっぱなしの状態が残ってしまいます。
import os
import tempfile
fd, path = tempfile.mkstemp(suffix=".txt")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write("自分で管理する一時ファイル")
print(path)
finally:
os.remove(path) # 削除は自分の責任
os.fdopenでファイル番号をファイルオブジェクトに変換すれば、閉じる処理をwith文に任せられます。削除はfinallyに書いて、途中で例外が起きても必ず実行されるようにしておきます。
一時ファイルはどこに作られるのか¶
tempfileが使う一時フォルダは、gettempdirという関数で確認できます。Linuxならたいてい/tmp、Windowsならユーザーごとの一時フォルダが返ってきます。
この場所は、決まった順番で候補を調べて決められています。最初に環境変数のTMPDIR、TEMP、TMPを順に確認し、どれもなければOSごとの定番の場所を試します。
それでも使える場所が見つからないときは、最後の手段として現在のフォルダが使われます。一時ファイルの置き場所を変えたいときは、環境変数を設定するか、関数ごとにdir引数でフォルダを指定しましょう。
容量とセキュリティに気をつける¶
一時フォルダは、OSやほかのプログラムと共有する場所です。大きなファイルを置くとディスクを圧迫しますし、サーバーによっては容量の小さい領域が割り当てられていることもあります。
私が以前担当した画像処理のシステムでは、数GBの中間ファイルを一時フォルダに書き出していました。本番サーバーの/tmpだけが小さく、深夜に容量不足で処理が止まったのは苦い思い出です。
大量のデータを扱うときは、dir引数で十分な容量のあるフォルダを指定するのが安全です。事前に本番環境の空き容量を確かめておく習慣も、身を守ってくれます。
やってはいけないこととテストでの使い方¶
最後に、tempfileまわりで避けたいことと、テストでの活用方法を紹介します。どちらも実務で出会う機会が多い話です。
mktempは使わない¶
tempfileにはmktempという関数も残っていますが、これは使ってはいけません。名前を決めるだけでファイルは作らないので、実際に作るまでのすきまに、ほかの誰かが同じ名前のファイルを作れてしまうからです。
公式ドキュメントでも非推奨とされ、ソースコードのコメントにはUNSAFEと大きく書かれています。名前だけ欲しいときも、NamedTemporaryFileかmkstempを使うのが正解です。
Windowsでは開き直しに注意¶
delete_on_closeを使わないNamedTemporaryFileは、Windowsで開いたまま別の処理から開き直すと、失敗することがあります。削除の仕組みがOSによって違うためです。
WindowsとMac、Linuxの両方で動かすスクリプトなら、3.12以上のdelete_on_close=Falseを使うのがいちばん確実です。チームで開発するときは、Windowsの人の環境でも一度動かしてもらいましょう。
pytestならtmp_pathが便利¶
テストコードの中で一時ファイルが必要なら、pytestのtmp_pathという仕組みも使えます。テスト関数の引数にtmp_pathと書くだけで、テストごとに専用の一時フォルダを用意してくれます。
仕組みとしてはfixtureという機能の一種で、準備と後片付けをpytestが肩代わりしてくれます。【関連記事】pytestのfixture入門|テスト前の準備処理をわかりやすく解説
テストの外ではtempfile、テストの中ではtmp_pathと覚えておくと、使い分けに迷いません。どちらも後片付けを人の記憶に頼らないという点で、考え方は同じです。
まとめ¶
今回は、Pythonのtempfileを使って、一時ファイルと一時フォルダを安全に扱う方法を紹介しました。自分で名前を付けて作るやり方に比べて、衝突も消し忘れも起きにくくなります。
迷ったら、名前が要らなければTemporaryFile、パスを渡すならNamedTemporaryFile、フォルダごと欲しければTemporaryDirectoryを選びましょう。どれもwith文と組み合わせるのが基本です。
一時ファイルは地味な存在ですが、きちんと片付けるかどうかでプログラムの信頼性は大きく変わります。次にtmp.txtと書きそうになったら、ぜひtempfileを思い出してみてください。
ここまでお読みいただきありがとうございました。