Help us understand the problem. What is going on with this article?

Pythonで一時ディレクトリを作って安全に後始末する

More than 1 year has passed since last update.

前置き:一時ファイルの作成

処理の途中で一時ファイルを作っていろいろ作業して、最後に削除したいということがありますよね。
こういう場合はTemporaryFileとかNamedTemporaryFileを使います。

Python2.7
import tempfile
with tempfile.TemporaryFile() as f:
    print >>f, "Test"
    f.seek(0)
    print f.read()
Python3.6
import tempfile
with tempfile.TemporaryFile("w+") as f:
    print("Test", file=f)
    f.seek(0)
    print(f.read())

Python 3だと一時ディレクトリも簡単

使うファイルが多いとか、外部ツールにファイルを作らせるとかで、一時ファイルではなくて一時ディレクトリを作りたいということがあるかもしれません。

Python 3(3.2以降)にはTemporaryDirectoryというものがあって、これを使うと一時ディレクトリを作ったうえで、使い終わったディレクトリを自動的に後始末してくれます。

Python3.6
import tempfile
import os
with tempfile.TemporaryDirectory() as dname:
    print(dname)                 # /tmp/tmpl2cvqpq5
    print(os.path.exists(dname)) # True
    with open(os.path.join(dname, "test.txt"), "w") as f:
        print("test", file=f)
print(os.path.exists(dname))     # False

with構文に指定した場合、たとえwith構文の中で例外が起きたとしても先に後始末してから死にます。
with構文をtry - except - finallyで囲んだ場合でも、例外が起きた時点でまず後始末されます。便利ですね。

しかしPython 2にはTemporaryDirectoryがありません。
mkdtempはありますが、自動で後始末はしてくれません。ただ一時ディレクトリを作ってくれるだけ。
ということで、Python 2でも後始末させましょう。

Python 2で一時ディレクトリ

要件

  • インスタンスが生成されたらmkdtempで一時ディレクトリを作成し、cleanupメソッドが呼ばれたら削除する
  • インスタンスのname属性から一時ディレクトリ名を取得する
  • with構文に指定した場合、asの後ろに指定した変数にはディレクトリ名(TemporaryDirectory.name相当)が入る。with構文から抜けたら自動的にcleanupメソッドを呼ぶ

Python 3の実装をベースに作ってみた

Python 3.6のライブラリの中から必要な部分を抜き出し、__init__のデフォルト引数をPython 2のmkdtempに寄せています。
その他、name属性を読み取り専用にするとか、ちょっと手を加えています。
(Linux環境なら、元ネタは /usr/lib/python3.6/tempfile.py あたりにあります)

Python2.7
import tempfile
import shutil
import os

class TemporaryDirectory(object):
    def __init__(self, suffix="", prefix="tmp", dir=None):
        self.__name = tempfile.mkdtemp(suffix, prefix, dir)

    def __enter__(self):
        return self.__name

    def __exit__(self, exc, value, tb):
        self.cleanup()

    @property
    def name(self):
        return self.__name

    def cleanup(self):
        shutil.rmtree(self.__name)

with TemporaryDirectory() as dname:
    print dname                 # /tmp/tmpslYr3Q
    print os.path.exists(dname) # True
    with open(os.path.join(dname, "test.txt"), "w") as f:
        print >>f, "test"
print os.path.exists(dname)     # False

実行結果はこんな感じ。

/tmp/tmpslYr3Q
True
False

使用例をもう一つ。例外を発生させてみます。

Python2.7
d = TemporaryDirectory()
print os.path.exists(d.name)
try:
    with d:
        print d.name
        d.name = "/tmp/123456"
finally:
    print d.name
    print os.path.exists(d.name)

実行結果はこうなります。(IPythonの場合)

True
/tmp/tmpJcC_mz
/tmp/tmpJcC_mz
False
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-17-372b6ae08491> in <module>()
      4     with d:
      5         print d.name
----> 6         d.name = "/tmp/123456"
      7 finally:
      8     print os.path.exists(dname)

AttributeError: can't set attribute

このように、nameに値を設定しようとすると例外が発生するようになっています。
また、例外が発生したときにfinallyのprint文が実行されますが、os.path.existsの結果がFalseになっていることから、finally節が実行される前に後始末が行われていることが分かります。

everylittle
PythonやWebプログラミングなどのTipsをメモ代わりに投稿しています。たまに機械学習の話題もあります。
https://www.every-little.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away