但是打开功能似乎有点清除文件
是的,以w 写入模式打开文件总是会清除文件;来自open() function documentation:
'w'
打开写入,先截断文件
[...]
默认模式为'r'(打开阅读文字,'rt'的同义词)。对于二进制读写访问,'w+b' 模式打开文件并将文件截断为 0 字节。 'r+b' 打开文件而不截断。
您希望在截断文件之前锁定文件。也可以'r+'模式(读写)打开文件,此时需要在锁定后手动截断。
您还需要锁定文件以进行读取,因为您不希望您的读者在另一个进程忙于替换内容时尝试读取时得到截断的数据。使用共享锁,此时其他进程也可以获取共享锁也,使得多个进程可以读取数据而无需相互等待。想要写入的进程必须获取排他锁,只有在没有共享锁时才会授予该锁。
就个人而言,我会创建一个上下文管理器来处理锁定(以独占模式进行写入,或以共享模式进行读取),并且仅在获得锁定后截断文件。您还需要考虑该文件尚不存在,如果您不想永远等待锁定,则需要处理超时(这意味着您需要在循环中使用 LOCK_NB 并测试返回值以查看是否获得了锁,直到经过一定时间)。
在以下上下文管理器中,我使用了os.open() 低级系统调用来确保在尝试锁定文件以进行独占访问时创建文件如果它已经存在则不截断它:
import errno
import fcntl
import os
import time
class Timeout(Exception):
"""Could not obtain a lock within the time given"""
class LockException(Exception):
"""General (file) locking-related exception"""
class LockedFile:
"""Lock and open a file.
If the file is opened for writing, an exclusive lock is used,
otherwise it is a shared lock
"""
def __init__(self, path, mode, timeout=None, **fileopts):
self.path = path
self.mode = mode
self.fileopts = fileopts
self.timeout = timeout
# lock in exclusive mode when writing or appending (including r+)
self._exclusive = set('wa+').intersection(mode)
self._lockfh = None
self._file = None
def _acquire(self):
if self._exclusive:
# open the file in write & create mode, but *without the
# truncate flag* to make sure it is created only if it
# doesn't exist yet
lockfhmode, lockmode = os.O_WRONLY | os.O_CREAT, fcntl.LOCK_EX
else:
lockfhmode, lockmode = os.O_RDONLY, fcntl.LOCK_SH
self._lockfh = os.open(self.path, lockfhmode)
start = time.time()
while True:
try:
fcntl.lockf(self._lockfh, lockmode | fcntl.LOCK_NB)
return
except OSError as e:
if e.errno not in {errno.EACCES, errno.EAGAIN}:
raise
if self.timeout is not None and time.time() - start > self.timeout:
raise Timeout()
time.sleep(0.1)
def _release(self):
fcntl.lockf(self._lockfh, fcntl.LOCK_UN)
os.close(self._lockfh)
def __enter__(self):
if self._file is not None:
raise LockException('Lock already taken')
self._acquire()
try:
self._file = open(self.path, self.mode, **self.fileopts)
except IOException:
self._release()
raise
return self._file
def __exit__(self, *exc):
if self._file is None:
raise LockException('Not locked')
try:
self._file.close()
finally:
self._file = None
self._release()
尝试读取文件的进程然后使用:
with LockedFile('test.json', 'r') as file:
data = json.load(file)
以及想写的进程使用:
with LockedFile('test.json', 'w') as file:
json.dump(data, file)
如果要允许超时,请在with 块周围添加try/except 块并捕获Timeout 异常;你需要决定接下来会发生什么:
try:
with LockedFile('test.json', 'w', timeout=10) as file:
json.dump(data, file)
except Timeout:
# could not acquire an exclusive lock to write the file. What now?