如何避免 makedirs 的竞争条件?

Nat*_*man 5 python directory race-condition

我正在尝试将以下伪代码转换为 Python:

如果 <目录> 不存在:
    为<目录>创建所有子目录
在 <目录> 中创建文件

os.makedirs这听起来很简单,可以用and来完成os.path.isdir

if not os.path.isdir('/some/path'):
    os.makedirs('/some/path')
open('/some/path/test.txt', 'w')
Run Code Online (Sandbox Code Playgroud)

然而,经过进一步检查,显然存在竞争条件。考虑以下时间表:

  1. 指定的目录 ( /some/path) 不存在
  2. Python 解释器执行第一行,其计算结果为True
  3. 另一个进程创建目录 ( /some/path)
  4. makedirsOSError由于目录已存在而引发异常

如果目录最初确实存在,但在执行最后一行之前被另一个进程删除,也会出现问题。

当谈到Python时,“请求原谅比请求许可更容易。” 考虑到这一点,上面的片段可以写得更好:

try:
    os.makedirs('/some/path')
except OSError:
    pass
open('/some/path/test.txt', 'w')
Run Code Online (Sandbox Code Playgroud)

这解决了上述两个问题,但产生了第三个问题:当发生以下情况之一时os.makedirs引发异常:OSError

  • 该目录已经存在
  • 无法创建目录

这意味着无法确定这两个条件中的哪一个导致了引发异常。换句话说,实际的失败将被默默地忽略,这不是我想要的。

我该如何解决这个问题?

roi*_*ppi 5

我会注意到,所有这些在 python 3 中都更加理智;FileExistsError和是您可以捕获的PermissionError单独( 的子类)异常,甚至当您对已经存在的目录感到满意时,甚至可以使用kwarg 来抑制前者。OSErroros.makedirsexist_ok

如果您想检查 的原因OSError,该信息位于元组中e.args(或者e.errno如果您只想查看错误代码,则可以选择):

try:
    os.makedirs('/etc/python')
except OSError as e:
    print e.args

(17, 'File exists')

try:
    os.makedirs('/etc/stuff')
except OSError as e:
    print e.args

(13, 'Permission denied')

try:
    os.makedirs('/etc/stuff')
except OSError as e:
    print e.errno

13
Run Code Online (Sandbox Code Playgroud)

因此,您必须进行一些内省,并在except块中以不同的方式处理两个错误代码。

  • 好主意!我可以使用 [`errno.EEXIST`](http://docs.python.org/2/library/errno.html#errno.EEXIST) 而不是硬编码 `17`。 (3认同)