在Python中打开文件以独占访问的最佳方式是什么?

什么是最优雅的方式来解决这个问题:

  • 打开一个文件阅读,但只有当它尚未打开写作
  • 打开一个文件进行写作,但只有当它还没有被打开进行阅读或写作

内置函数就像这样工作

>>> path = r"c:\scr.txt" >>> file1 = open(path, "w") >>> print file1 <open file 'c:\scr.txt', mode 'w' at 0x019F88D8> >>> file2 = open(path, "w") >>> print file2 <open file 'c:\scr.txt', mode 'w' at 0x02332188> >>> file1.write("111") >>> file2.write("222") >>> file1.close() 

scr.txt现在包含'111'。

 >>> file2.close() 

scr.txt被覆盖,现在包含'222'(在Windows上,Python 2.4)。

解决scheme应该在同一个进程内工作(就像上面的例子一样)以及另一个进程打开文件的时候。
最好是,如果一个崩溃的程序不会保持锁打开。

我不认为有一个完全跨平台的方式。 在unix上,fcntl模块会为你做这个。 然而,在Windows(我认为你是由path),你需要使用win32file模块。

幸运的是,在python食谱中有一个使用平台适当方法的可移植实现( portalocker )。

要使用它,打开文件,然后调用:

 portalocker.lock(file, flags) 

其中标志为用于独占写入访问的portalocker.LOCK_EX或用于共享读取访问的LOCK_SH。

解决scheme应该在同一个进程内工作(就像上面的例子一样)以及另一个进程打开文件的时候。

如果通过“另一个进程”,你的意思是“任何进程”(即不是你的程序),在Linux中,没有办法只依靠系统调用( fcntl &friends)来完成这个任务。 你想要的是强制性的locking ,而Linux的获取方式则涉及更多一点:

mand选项重新安装包含文件的分区:

 # mount -o remount,mand /dev/hdXY 

为你的文件设置sgid标志:

 # chmod gx,g+s yourfile 

在您的Python代码中,获取该文件的排他锁:

 fcntl.flock(fd, fcntl.LOCK_EX) 

现在,即使放开锁,也不能读取文件。

这是一个可移植实现的win32的一个开始,不需要单独的locking机制。

要求Windows扩展的Python可以使用win32 api,但对于Windows上的python来说 ,这是非常必要的,也可以使用ctypes来完成。 如果需要的话,可以调整代码以暴露更多的function(比如允许FILE_SHARE_READ而不是共享)。 另请参阅CreateFileWriteFile系统调用的MSDN文档以及有关创build和打开文件的文章 。

如前所述,如果需要,可以使用标准的fcntl模块实现unix的一半。

 import winerror, pywintypes, win32file class LockError(StandardError): pass class WriteLockedFile(object): """ Using win32 api to achieve something similar to file(path, 'wb') Could be adapted to handle other modes as well. """ def __init__(self, path): try: self._handle = win32file.CreateFile( path, win32file.GENERIC_WRITE, 0, None, win32file.OPEN_ALWAYS, win32file.FILE_ATTRIBUTE_NORMAL, None) except pywintypes.error, e: if e[0] == winerror.ERROR_SHARING_VIOLATION: raise LockError(e[2]) raise def close(self): self._handle.close() def write(self, str): win32file.WriteFile(self._handle, str) 

以上是你上面的例子的行为:

 >>> path = "C:\\scr.txt" >>> file1 = WriteLockedFile(path) >>> file2 = WriteLockedFile(path) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... LockError: ... >>> file1.write("111") >>> file1.close() >>> print file(path).read() 111 

编辑: 我自己解决了! 通过使用目录存在和年龄作为locking机制! 由文件locking只在Windows上安全(因为Linux默默覆盖),但在目录locking目录完美的作品在Linux和Windows上。 看到我的GIT,我创build了一个易于使用的类'lockbydir.DLock'

https://github.com/drandreaskrueger/lockbydir

在自述文件的底部,您会发现3个GITplayers,您可以在浏览器中查看代码示例! 很酷,不是吗? 🙂

感谢您的关注


这是我原来的问题:

我想回答parity3( https://meta.stackoverflow.com/users/1454536/parity3 ),但我既不能直接评论('你必须有50个评论的声望'),我也没有看到任何联系方式他/她直接。 你有什么build议给我,去找他?

我的问题:

我已经实现了类似于奇偶校验3在这里提供的答案: https ://stackoverflow.com/a/21444311/3693375(“假设你的Python解释器,和…”)

它在Windows上非常出色。 (我正在使用它来实现一个locking机制,跨独立启动的进程。https://github.com/drandreaskrueger/lockbyfile

但奇偶校验3说,它在Linux上不起作用:

os.rename(src,dst)

将文件或目录src重命名为dst。 …在Unix上,如果dst存在并且是一个文件,那么如果用户有权限,它将被静静地replace。 如果src和dst位于不同的文件系统上,操作可能会失败。 如果成功,重命名将是一个primefaces操作(这是一个POSIX的要求)。 在Windows上,如果dst已经存在,则会引发OSError( https://docs.python.org/2/library/os.html#os.rename

沉默replace是问题。 在Linux上。 “如果dst已经存在,OSError将会被唤醒”对于我来说是非常棒的。 但只有在Windows上,可悲的是。

我想奇迹3的例子在大多数情况下仍然有效,因为他的条件

 if not os.path.exists(lock_filename): try: os.rename(tmp_filename,lock_filename) 

但是整个事情不再是primefaces了。

因为if条件在两个并行进程中可能是真实的,那么两者都将重新命名,但只有一个才能赢得重命名的比赛。 而且没有例外(在Linux中)。

有什么build议么? 谢谢!

PS:我知道这不是正确的方法,但我缺less一个select。 请不要惩罚我降低我的声誉。 我环顾四周,自己解决这个问题。 如何在这里PM用户? 而且我为什么不能?

为了在一个应用程序中打开文件时的安全,您可以尝试如下所示:

 import time class ExclusiveFile(file): openFiles = {} fileLocks = [] class FileNotExclusiveException(Exception): pass def __init__(self, *args): sMode = 'r' sFileName = args[0] try: sMode = args[1] except: pass while sFileName in ExclusiveFile.fileLocks: time.sleep(1) ExclusiveFile.fileLocks.append(sFileName) if not sFileName in ExclusiveFile.openFiles.keys() or (ExclusiveFile.openFiles[sFileName] == 'r' and sMode == 'r'): ExclusiveFile.openFiles[sFileName] = sMode try: file.__init__(self, sFileName, sMode) finally: ExclusiveFile.fileLocks.remove(sFileName) else: ExclusiveFile.fileLocks.remove(sFileName) raise self.FileNotExclusiveException(sFileName) def close(self): del ExclusiveFile.openFiles[self.name] file.close(self) 

这样你就可以inheritancefile类。 现在就做:

 >>> f = ExclusiveFile('/tmp/a.txt', 'r') >>> f <open file '/tmp/a.txt', mode 'r' at 0xb7d7cc8c> >>> f1 = ExclusiveFile('/tmp/a.txt', 'r') >>> f1 <open file '/tmp/a.txt', mode 'r' at 0xb7d7c814> >>> f2 = ExclusiveFile('/tmp/a.txt', 'w') # can't open it for writing now exclfile.FileNotExclusiveException: /tmp/a.txt 

如果你先用'w'模式打开它,它将不允许再打开,即使在读取模式下,就像你想的那样…

假设你的Python解释器,以及底层的os和文件系统把os.rename当作一个primefaces操作,当目的地存在时它将会出错,下面的方法没有竞争条件。 我正在linux机器上使用它。 不需要第三方库,也不依赖于操作系统,除了创build额外的文件之外,对于许多用例来说性能指标是可以接受的。 你可以很容易地应用Python的函数装饰器模式或一个'with_statement'上下文pipe理器来抽象出混乱。

您需要确保在新进程/任务开始之前lock_filename不存在。

 import os,time def get_tmp_file(): filename='tmp_%s_%s'%(os.getpid(),time.time()) open(filename).close() return filename def do_exclusive_work(): print 'exclusive work being done...' num_tries=10 wait_time=10 lock_filename='filename.lock' acquired=False for try_num in xrange(num_tries): tmp_filename=get_tmp_file() if not os.path.exists(lock_filename): try: os.rename(tmp_filename,lock_filename) acquired=True except (OSError,ValueError,IOError), e: pass if acquired: try: do_exclusive_work() finally: os.remove(lock_filename) break os.remove(tmp_filename) time.sleep(wait_time) assert acquired, 'maximum tries reached, failed to acquire lock file' 

编辑

已经发现os.rename默默地覆盖了非windows操作系统上的目的地。 感谢您指出@ akrueger!

这里是一个解决方法,从这里收集:

而不是使用os.rename,你可以使用:

 try: if os.name != 'nt': # non-windows needs a create-exclusive operation fd = os.open(lock_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL) os.close(fd) # non-windows os.rename will overwrite lock_filename silently. # We leave this call in here just so the tmp file is deleted but it could be refactored so the tmp file is never even generated for a non-windows OS os.rename(tmp_filename,lock_filename) acquired=True except (OSError,ValueError,IOError), e: if os.name != 'nt' and not 'File exists' in str(e): raise 

@ akrueger对于基于目录的解决scheme,您可能没有问题,只是给你一个替代方法。