【问题标题】:`shutil.rmtree` does not work on `tempfile.TemporaryDirectory()``shutil.rmtree` 不适用于 `tempfile.TemporaryDirectory()`
【发布时间】:2018-06-01 19:06:49
【问题描述】:

考虑一下这个测试

import shutil, tempfile
from os import path
import unittest

from pathlib import Path

class TestExample(unittest.TestCase):
    def setUp(self):
        # Create a temporary directory
        self.test_dir = tempfile.TemporaryDirectory()
        self.test_dir2 = tempfile.mkdtemp()

    def tearDown(self):
        # Remove the directory after the  test
        shutil.rmtree(self.test_dir2) 
        shutil.rmtree(self.test_dir.name) #throws error

    def test_something(self):
        self.assertTrue(Path(self.test_dir.name).is_dir())
        self.assertTrue(Path(self.test_dir2).is_dir())

if __name__ == '__main__':
    unittest.main()

tearDown 中却引发了错误

FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpxz7ts7a7'

指的是self.test_dir.name

根据the source code for tempfile,两个元素是一样的。

    def __init__(self, suffix=None, prefix=None, dir=None):
        self.name = mkdtemp(suffix, prefix, dir)
        self._finalizer = _weakref.finalize(
            self, self._cleanup, self.name,
            warn_message="Implicitly cleaning up {!r}".format(self))

而且我没有在上下文中使用它,所以据我所知,不应调用 __exit__()

发生了什么?

【问题讨论】:

  • 请不要更改tempfile 的标签,这是正在使用的python 模块。 temporary-files 可能会产生误导。
  • 我最后试了一下,效果很好:/
  • 一个TemporaryDirectory在相应对象被垃圾回收时自动删除。也就是说,我不知道为什么在tearDown 函数完成执行之前 会对其进行垃圾收集。
  • 您确定它是 shutil.rmtree(self.test_dir.name) 而不是 self.test_dir 的终结器吗?您是否尝试过 not 来清理它,在退出上下文/垃圾收集时它不是被清理了吗? “在临时目录对象的上下文或销毁完成后,新创建的临时目录及其所有内容都将从文件系统中删除。”

标签: python python-3.x python-unittest temporary-files shutil


【解决方案1】:

不要用shutil 清理这些。 tempfile.TemporaryDirectory 类提供了一个 cleanup() 方法,如果您想选择显式清理,只需调用它即可。

代码崩溃的原因是TemporaryDirectory 类被设计为在超出范围后自行清理(参考计数为零)。但是,由于您已经手动从文件系统中删除了该目录,因此当实例随后尝试删除自身时,拆除会失败。 “没有这样的文件或目录”错误来自TemporaryDirectory 自己的拆解,而不是来自您的shutil.rmtree 行!

【讨论】:

  • 我不明白TemporaryDirectory class is designed to clean up after itself once it goes out of scope (ref count to zero) 的意思。自清洁是在调用tearDown 之后还是之前发生?这是否意味着UnitTestCase 在一些context 中是一个巨大的try/finally 块?
  • 它发生在调用tearDown 之后,因为测试类本身持有对TemporaryDirectory 实例的引用(通过self.test_dir)。只有在删除测试(即“self”)后,才能最终确定临时目录。是的,unittest.TestCase 的执行或多或少是由测试运行程序设置的巨大的 try/finally 块。
  • 这是一个很好的例子,说明为什么查看完整的堆栈跟踪很重要 - 太多人只是查看错误并假设他们知道它来自哪里
  • 我不明白为什么self.test_dircleanup() 方法会在tearDown 的末尾被调用。我想这是一个不同的问题。当前的两个答案都正确解释了问题,所以如果您不介意,我想我明天会选择一个答案。
  • @bluesmonk 因为TemporaryDirectory()__init__ 中设置了一个weakref finalizer,当对象被垃圾回收时它调用cleanup()。一旦没有对它的引用,即一旦测试完成,就会收集该对象。
【解决方案2】:

这与上下文无关:

import tempfile,os

t = tempfile.TemporaryDirectory()
s = t.name
print(os.path.isdir(s))
# os.rmdir(s) called here triggers error on the next line
t = None
print(os.path.isdir(s))

打印出来

True
False

因此,一旦t 的引用设置为None,对象就会被垃圾收集并删除目录,正如documentation 所述:

在上下文或临时目录对象销毁完成后,新创建的临时目录及其所有内容将从文件系统中删除。

当对象完成时,在下面的 sn-p 中取消注释 os.rmdir(s) 会引发异常:

Exception ignored in: <finalize object at 0x20b20f0; dead>
Traceback (most recent call last):
  File "L:\Python34\lib\weakref.py", line 519, in __call__
    return info.func(*info.args, **(info.kwargs or {}))
  File "L:\Python34\lib\tempfile.py", line 698, in _cleanup
    _shutil.rmtree(name)
  File "L:\Python34\lib\shutil.py", line 482, in rmtree
    return _rmtree_unsafe(path, onerror)
  File "L:\Python34\lib\shutil.py", line 364, in _rmtree_unsafe
    onerror(os.listdir, path, sys.exc_info())
  File "L:\Python34\lib\shutil.py", line 362, in _rmtree_unsafe
    names = os.listdir(path)

所以你的调用可能成功了,但是你在对象的最终确定时得到了异常(就在之后)

调用cleanup() 对象方法而不是rmtree 解决了这个问题,因为对象内部状态已更新,not 在完成时尝试删除目录(如果你问我,对象应该在尝试清理之前测试目录是否存在,但即使这样也并不总是有效,因为它不是原子操作)

所以替换

shutil.rmtree(self.test_dir.name)

通过

self.test_dir.cleanup()

或者什么都不做,让对象在删除时清理目录。

【讨论】:

  • 因此,如果我得到正确的订单,在tearDown 完成后,temp_dir 调用运行cleanup()__exit__ 方法,失败是因为在tearDown 调用期间,该文件夹被删除使用shutil.rmtree()
  • 是的,可能会发生什么。愚蠢的是对象调用rmtree 而不检查基目录是否存在......
  • 对于这是否是“愚蠢的”,或者实际上是一个明智的设计决定,意见不同。它可以防止您错误地使用该类,而不会注意到上下文是使用弱引用自动管理的(此外,“非托管”版本是 tempfile.mkdtemp)。
  • 可能这样设计以避免竞争条件:测试目录是否存在并在之后删除它并不能保证,因为它可以同时被删除。另一种方法是捕获异常,然后区分“由于文件被锁定而无法删除”和“由于文件不存在而无法删除”。点了。
猜你喜欢
  • 2014-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-25
  • 1970-01-01
相关资源
最近更新 更多