Python中创建文件夹的竞争条件
我有一个用来缓存的urllib2模块,但它偶尔会因为以下代码崩溃:
if not os.path.exists(self.cache_location):
os.mkdir(self.cache_location)
问题是,当第二行代码执行时,文件夹可能已经存在,这样就会出错:
File ".../cache.py", line 103, in __init__ os.mkdir(self.cache_location) OSError: [Errno 17] File exists: '/tmp/examplecachedir/'
这是因为这个脚本会被我无法控制的第三方代码同时多次启动。
在我尝试修复这个错误之前的代码可以在这里找到,GitHub上
我不能使用tempfile.mkstemp,因为它通过使用随机命名的目录来解决竞争条件,这样就失去了缓存的意义。
我不想简单地忽略这个错误,因为如果文件夹的名字已经存在为一个文件(这是一个不同的错误),同样会出现Errno 17错误,例如:
$ touch blah $ python >>> import os >>> os.mkdir("blah") Traceback (most recent call last): File "", line 1, in OSError: [Errno 17] File exists: 'blah' >>>
我不能使用threading.RLock
,因为这个代码是从多个进程中调用的。
所以,我尝试写一个简单的基于文件的锁(这个版本可以在这里找到),但这有个问题:它在上一级目录创建锁文件,所以对于/tmp/example/
来说,它会创建/tmp/example.lock
,如果你把/tmp/
作为缓存目录使用,就会出错(因为它试图创建/tmp.lock
)。
总之,我需要将urllib2
的响应缓存到磁盘。为此,我需要以多进程安全的方式访问一个已知的目录(如果需要的话创建它)。这个方法需要在OS X、Linux和Windows上都能工作。
有什么想法吗?我能想到的唯一替代方案是用SQLite3存储重写缓存模块,而不是使用文件。
5 个回答
我最后写出来的代码是:
import os
import errno
folder_location = "/tmp/example_dir"
try:
os.mkdir(folder_location)
except OSError as e:
if e.errno == errno.EEXIST and os.path.isdir(folder_location):
# File exists, and it's a directory,
# another process beat us to creating this dir, that's OK.
pass
else:
# Our target dir exists as a file, or different error,
# reraise the error!
raise
与其使用
if not os.path.exists(self.cache_location):
os.mkdir(self.cache_location)
你可以这样做
try:
os.makedirs(self.cache_location)
except OSError:
pass
这样做的结果是功能是一样的。
免责声明:我不知道这样做在Python中是否合适。
使用 SQLite3
可能有点过于复杂,但它能为你的需求增加很多功能和灵活性。
如果你需要频繁地进行“选择”、同时插入和过滤数据,使用 SQLite3
是个不错的主意,因为它不会比简单的文件增加太多复杂性(甚至可以说它能减少复杂性)。
重新阅读你的问题(和评论)后,我能更好地理解你的问题。
有没有可能一个文件也会产生相同的竞争条件?
如果文件足够小,我会这样做:
if not os.path.isfile(self.cache_location):
try:
os.makedirs(self.cache_location)
except OSError:
pass
另外,看看你的代码,我会把
else:
# Our target dir is already a file, or different error,
# relay the error!
raise OSError(e)
改成
else:
# Our target dir is already a file, or different error,
# relay the error!
raise
因为这正是你想要的,让Python重新抛出相同的异常(只是挑剔一下)。
还有一件事,也许这个对你有帮助(仅限类Unix系统)。
在Python 3.x中,你可以使用 os.makedirs(path, exist_ok=True)
这个命令来创建文件夹。如果你要创建的文件夹已经存在,它不会报错。可是,如果有一个文件的名字和你想创建的文件夹名字一样,那么就会出现 FileExistsError: [Errno 17]
的错误。
你可以用下面的代码来验证一下:
import os
parent = os.path.dirname(__file__)
target = os.path.join(parent, 'target')
os.makedirs(target, exist_ok=True)
os.makedirs(target, exist_ok=True)
os.rmdir(target)
with open(target, 'w'):
pass
os.makedirs(target, exist_ok=True)