如何使文件创建成为一个原子操作?
- 2025-03-05 09:15:00
- admin 原创
- 2
问题描述:
我正在使用 Python 通过一次操作将大块文本写入文件:
open(file, 'w').write(text)
如果脚本中断导致文件写入未完成,我希望没有文件,而不是部分完成的文件。可以这样做吗?
解决方案 1:
将数据写入临时文件,当数据成功写入后,将文件重命名为正确的目标文件,例如
with open(tmpFile, 'w') as f:
f.write(text)
# make sure that all data is on disk
# see http://stackoverflow.com/questions/7433057/is-rename-without-fsync-safe
f.flush()
os.fsync(f.fileno())
os.replace(tmpFile, myFile) # os.rename pre-3.3, but os.rename won't work on Windows
根据文档http://docs.python.org/library/os.html#os.replace
将文件或目录重命名
src
为dst
。如果 dst 是非空目录,OSError
则会引发 。如果dst
存在且是文件,则在用户有权限的情况下会默默替换它。如果src
和dst
位于不同的文件系统上,则操作可能会失败。如果成功,重命名将是一个原子操作(这是 POSIX 要求)。
笔记:
如果源和目标位置不在同一文件系统上,则可能不是原子操作
os.fsync
如果在电源故障、系统崩溃等情况下性能/响应能力比数据完整性更重要,则可以跳过此步骤
解决方案 2:
一个使用 Python 实现原子写入的简单代码片段tempfile
。
with open_atomic('test.txt', 'w') as f:
f.write("huzza")
甚至可以读取和写入同一个文件:
with open('test.txt', 'r') as src:
with open_atomic('test.txt', 'w') as dst:
for line in src:
dst.write(line)
使用两个简单的上下文管理器
import os
import tempfile as tmp
from contextlib import contextmanager
@contextmanager
def tempfile(suffix='', dir=None):
""" Context for temporary file.
Will find a free temporary filename upon entering
and will try to delete the file on leaving, even in case of an exception.
Parameters
----------
suffix : string
optional file suffix
dir : string
optional directory to save temporary file in
"""
tf = tmp.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir)
tf.file.close()
try:
yield tf.name
finally:
try:
os.remove(tf.name)
except OSError as e:
if e.errno == 2:
pass
else:
raise
@contextmanager
def open_atomic(filepath, *args, **kwargs):
""" Open temporary file object that atomically moves to destination upon
exiting.
Allows reading and writing to and from the same filename.
The file will not be moved to destination in case of an exception.
Parameters
----------
filepath : string
the file path to be opened
fsync : bool
whether to force write the file to disk
*args : mixed
Any valid arguments for :code:`open`
**kwargs : mixed
Any valid keyword arguments for :code:`open`
"""
fsync = kwargs.pop('fsync', False)
with tempfile(dir=os.path.dirname(os.path.abspath(filepath))) as tmppath:
with open(tmppath, *args, **kwargs) as file:
try:
yield file
finally:
if fsync:
file.flush()
os.fsync(file.fileno())
os.rename(tmppath, filepath)
解决方案 3:
由于细节很容易搞砸,我建议使用一个小型库。库的优点是它能处理所有这些细节,并且由社区进行审查和改进。
untitakerpython-atomicwrites
编写的其中一个库甚至对 Windows 有适当的支持:
警告(截至 2023 年):
此库目前无人维护。作者评论:
[...],我认为是时候弃用这个包了。Python 3 有 os.replace 和 os.rename,它们可能足以应付大多数用例。
原文推荐:
摘自自述文件:
from atomicwrites import atomic_write
with atomic_write('foo.txt', overwrite=True) as f:
f.write('Hello world.')
# "foo.txt" doesn't exist yet.
# Now it does.
通过 PIP 安装:
pip install atomicwrites
解决方案 4:
我正在使用此代码以原子方式替换/写入文件:
import os
from contextlib import contextmanager
@contextmanager
def atomic_write(filepath, binary=False, fsync=False):
""" Writeable file object that atomically updates a file (using a temporary file).
:param filepath: the file path to be opened
:param binary: whether to open the file in a binary mode instead of textual
:param fsync: whether to force write the file to disk
"""
tmppath = filepath + '~'
while os.path.isfile(tmppath):
tmppath += '~'
try:
with open(tmppath, 'wb' if binary else 'w') as file:
yield file
if fsync:
file.flush()
os.fsync(file.fileno())
os.rename(tmppath, filepath)
finally:
try:
os.remove(tmppath)
except (IOError, OSError):
pass
用法:
with atomic_write('path/to/file') as f:
f.write("allons-y!
")
它是基于这个食谱的。
解决方案 5:
完成后只需链接文件:
with tempfile.NamedTemporaryFile(mode="w") as f:
f.write(...)
os.link(f.name, final_filename)
如果你想变得更时尚:
@contextlib.contextmanager
def open_write_atomic(filename: str, **kwargs):
kwargs['mode'] = 'w'
with tempfile.NamedTemporaryFile(**kwargs) as f:
yield f
os.link(f.name, filename)
解决方案 6:
本页上的答案相当老了,现在有一些图书馆可以为您做到这一点。
特别是,safer
这是一个旨在帮助防止程序员错误破坏文件、套接字连接或通用流的库。它非常灵活,除其他功能外,它还可以选择使用内存或临时文件,您甚至可以在发生故障时保留临时文件。
他们的例子正是你想要的:
# dangerous
with open(filename, 'w') as fp:
json.dump(data, fp)
# If an exception is raised, the file is empty or partly written
# safer
with safer.open(filename, 'w') as fp:
json.dump(data, fp)
# If an exception is raised, the file is unchanged.
它在 PyPI 中,只需使用安装即可,或者在https://github.com/rec/saferpip install --user safer
获取最新版本
解决方案 7:
适用于 Windows 的循环文件夹和重命名文件的原子解决方案。经过测试,原子自动化,您可以增加概率以最大限度地降低发生相同文件名事件的风险。您可以使用 random.choice 方法随机化字母符号组合库,对于数字 str(random.random.range(50,999999999,2)。您可以根据需要改变数字范围。
import os import random
path = "C:\\Users\\ANTRAS\\Desktop\\NUOTRAUKA\\\"
def renamefiles():
files = os.listdir(path)
i = 1
for file in files:
os.rename(os.path.join(path, file), os.path.join(path,
random.choice('ABCDEFGHIJKL') + str(i) + str(random.randrange(31,9999999,2)) + '.jpg'))
i = i+1
for x in range(30):
renamefiles()
- 2025年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)