web-dev-qa-db-ja.com

Pythonクラスの一時フォルダをクリーンアップする正しい方法

オブジェクトの存続期間中存続し、その後削除されるフォルダーの一時ワークスペースを生成するクラスを作成しています。 def initでtempfile.mkdtemp()を使用してスペースを作成していますが、delの呼び出しに依存できないことを読みました。

私はこのようなものが欲しいです:

class MyClass:
  def __init__(self):
    self.tempfolder = tempfile.mkdtemp()

  def ... #other stuff

  def __del__(self):
    if os.path.exists(self.tempfolder): shutil.rmtree(self.tempfolder)

このクリーンアップを処理する別の/より良い方法はありますか? 「with」について読んでいましたが、関数内でのみ役立つようです。

22
Rob Hunter

警告:ユーザーは常にプロセスを強制終了でき、それ以外は何も実行できないため、一時フォルダーが削除されることは決して保証できません。

とは言っても

temp_dir = tempfile.mkdtemp()
try:
    <some code>
finally:
    shutil.rmtree(temp_dir)

これは非常に一般的な操作であるため、Pythonには、「何かを実行し、コードを実行し、クリーンアップする」ための特別な方法があります。aコンテキストマネージャです。次のように所有します:

@contextlib.contextmanager
def make_temp_directory():
    temp_dir = tempfile.mkdtemp()
    try:
        yield temp_dir
    finally:
        shutil.rmtree(temp_dir)

そしてそれを

with make_temp_directory() as temp_dir:
    <some code>

(これは@contextlib.contextmanagerショートカットを使用してコンテキストマネージャーを作成することに注意してください。元の方法で実装する場合は、__enter__および__exit__メソッドを使用してカスタムクラスを作成する必要があります。 __enter__は一時ディレクトリを作成して返し、__exit__はそれを削除します。

40
Katriel

一時ファイルとディレクトリを処理するための良い方法は、コンテキストマネージャを使用することです。これは tempfile.TemporaryFile または tempfile.NamedTemporaryFile -withステートメントを終了した後(通常のexit、return、exceptionを使用する方法)です。 、またはその他)ファイル/ディレクトリとその内容がファイルシステムから削除されます。

Python 3.2+の場合、これは tempfile.TemporaryDirectory として組み込まれています。

_import tempfile

with tempfile.TemporaryDirectory() as temp_dir:
    ... do stuff ...
_

以前のPythonバージョンでは、まったく同じことを行うために独自のコンテキストマネージャを簡単に作成できます。ここでの@katrielalexの回答との違いは、mkdtemp()への引数の受け渡しと例外が発生した場合にディレクトリが確実にクリーンアップされるように、try/finallyブロックします。

_import contextlib
import shutil

@contextlib.contextmanager
def temporary_directory(*args, **kwargs):
    d = tempfile.mkdtemp(*args, **kwargs)
    try:
        yield d
    finally:
        shutil.rmtree(d)


# use it
with temporary_directory() as temp_dir:
    ... do stuff ...
_

プロセスが強制終了された場合(_kill -9_など)、ディレクトリはクリーンアップされないことに注意してください。

11
rcoup

contextlibを使用するもう1つの方法は、オブジェクトをクローズ可能にし、 closing コンテキストマネージャを使用することです。

class MyClass:
    def __init__(self):
        self.tempfolder = tempfile.mkdtemp()

    def do_stuff():
        pass

    def close(self):
        if os.path.exists(self.tempfolder):
            shutil.rmtree(self.tempfolder)

次に、コンテキストマネージャを使用します。

from contextlib import closing

with closing(MyClass()) as my_object:
    my_object.do_stuff()
2
Peter Wood

Bluewind で述べられているように、必ずコンテキストマネージャのyield部分をtry:finallyステートメント内にラップする必要があります。そうしないと、例外は実際にはコンテキストマネージャ内で正しく処理されません。

から Python 2.7 docs

ジェネレーターが生成される時点で、withステートメントにネストされたブロックが実行されます。その後、ブロックが終了すると、ジェネレーターが再開されます。未処理の例外がブロックで発生した場合は、yieldが発生した時点でジェネレーター内で再発生します。したがって、try ... except ... finallyステートメントを使用してエラー(ある場合)をトラップするか、何らかのクリーンアップが行われるようにすることができます。例外をログに記録するため、または(完全に抑制するためではなく)何らかのアクションを実行するためだけに例外がトラップされる場合、ジェネレータはその例外を再発生させる必要があります。それ以外の場合、ジェネレータコンテキストマネージャは、例外が処理されたことをwithステートメントに示し、withステートメントの直後のステートメントから実行が再開されます。

また、Python 3.2+を使用している場合は、チェックアウトする必要があります この小さな宝石 上記のすべてをうまくまとめています

tempfile.TemporaryDirectory(suffix = ''、prefix = 'tmp'、dir = None)

この関数は、mkdtemp()を使用して一時ディレクトリを作成します(提供された引数は、基になる関数に直接渡されます)。結果のオブジェクトは、コンテキストマネージャーとして使用できます(ステートメントコンテキストマネージャーありを参照)。コンテキストが完了すると(または一時ディレクトリオブジェクトが破棄されると)、新しく作成された一時ディレクトリとそのすべてのコンテンツがファイルシステムから削除されます。

ディレクトリ名は、返されたオブジェクトのname属性から取得できます。

Cleanup()メソッドを呼び出すことにより、ディレクトリを明示的にクリーンアップできます。

バージョン3.2の新機能。

2
Necrolyte2

他の回答では、コンテキストマネージャーを使用したり、ユーザーに特定の種類のクリーンアップ関数を明示的に呼び出すように要求したりできると述べています。できる場合は、これらを実行すると便利です。ただし、大規模なアプリケーションの内部にいて、複数のレイヤーを入れ子にしているため、このクリーンアップを接続する場所がない場合があり、クリーンアップメソッドやコンテキストマネージャーを上に置くものはありません。

その場合、atexitを使用できます: https://docs.python.org/2/library/atexit.html

import atexit

class MyClass:
  def __init__(self):
    self.tempfolder = tempfile.mkdtemp()
    atexit.register(shutil.rmtree, self.tempfolder)

  def ... #other stuff
1
crouleau