清理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的文章,但它似乎只在函数中有用。

注意事项:您永远不能保证临时文件夹会被删除,因为用户可能总是硬杀死您的进程,然后它就无法运行其他任何东西。

也就是说,做

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

由于这是一个非常常见的操作,Python有一种特殊的方式来封装"做某事,执行代码,清理":上下文管理器。你可以写自己的如下:

@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__会删除它。

处理临时文件和目录的一个好方法是通过上下文管理器。这就是使用tempfile的方法。TemporaryFile或tempfile。NamedTemporaryFile——一旦退出with语句(通过正常退出、返回、异常或其他任何方式),文件/目录及其内容将从文件系统中删除。

对于Python 3.2+,这是作为tempfile内置的。临时目录:

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

对于早期的Python版本,您可以轻松地创建自己的上下文管理器来执行完全相同的操作。这里与@katrielalex answer的区别在于将args传递给mkdtemp(),以及try/finaly块,以确保在引发异常时清理目录。

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),那么目录将不会被清理。

我对此进行了一些实验,我很有信心,如果您不能使用上下文管理器,那么截至本文发布之时,最佳解决方案是:

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

…
def __del__(self):
self.tempfolder.cleanup()

(如果不能确保调用__init____del__中的某些条件可能是合理的。)

现在,除了使用较新的TemporaryDirectory而不是mkdtemp之外,这与您以前所做的没有太大区别。为什么我仍然认为这是你能做的最好的事情?好吧,我测试了几个程序退出和类似的场景(都在Linux上)和:

  • 我找不到不删除临时文件夹的场景,尽管我希望Python可以决定不再需要MyClass的相应实例。在Python的垃圾收集启发法允许的情况下,自动删除会尽早发生。

  • 您可以用del myinstancegc.collect()"帮助"垃圾收集器。请注意,del只会减少引用计数,因此这并不能确保垃圾回收和调用__del__

  • 如果您确实希望确保删除(临时目录),可以显式调用myinstance.__del__()。如果你能做到这一点,你可能还可以让MyClass本身成为一个上下文管理器。

  • 临时文件夹持久存在的唯一情况是我从操作系统中硬杀Python——在这种情况下,我看不出Python中的任何解决方案会如何工作。

  • atexit(如这个答案所建议的)并不能改善这种情况:要么删除在没有atexit的情况下发生,要么即使有atexit也不会发生。

使用contextlib的另一种选择是使对象可关闭,并使用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()

其他答案已经指出,您可以使用上下文管理器或要求用户显式调用某种类型的清理函数。如果可以的话,这些都很棒。然而,有时没有地方可以连接这种清理,因为您在一个大型应用程序中,并且您嵌套在多个层之下,而且您上面的任何人都没有清理方法或上下文管理器。

在这种情况下,您可以使用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

正如Bluewind所说,您必须确保将上下文管理器的yield部分封装在try:finally语句中,否则任何异常都不会在上下文管理器中得到正确处理。

来自Python 2.7文档

在生成器生成时,执行嵌套在with语句中的块。然后,在块退出后,将恢复生成器。如果块中发生未处理的异常,则在生成程序中发生屈服点时会重新引发该异常。因此,您可以使用try。。。除了finally语句捕获错误(如果有的话),或者确保进行一些清理。如果捕获异常只是为了记录它或执行某些操作(而不是完全抑制它),则生成器必须重新评估该异常。否则,生成器上下文管理器将向with语句指示异常已被处理,并在with语句之后立即使用该语句恢复执行。

此外,如果你使用的是Python 3.2+,你应该看看这个小宝石,它已经很好地为你完成了以上所有的

tempfile.TemporaryDirectory(后缀='',前缀='tmp',目录=无)

此函数使用mkdtemp()创建一个临时目录(提供的参数直接传递给底层函数)。生成的对象可以用作上下文管理器(请参阅With Statement context Managers)。在完成上下文(或销毁临时目录对象)后,将从文件系统中删除新创建的临时目录及其所有内容。

目录名称可以从返回对象的name属性中检索。

可以通过调用cleanup()方法来显式清理目录。

3.2版新增。

相关内容

最新更新