我正在使用Python一次性将文本块写入文件:
open(file, 'w').write(text)
如果脚本被中断以致于文件写入未能完成,我希望得到的是没有文件而不是部分完成的文件。这可行吗?
将数据写入临时文件,当数据成功写入后,将文件重命名为正确的目标文件,例如:
with open(tmpFile, 'w') as f:
f.write(text)
# make sure that all data is on disk
# see https://dev59.com/cGs05IYBdhLWcg3wIObS
f.flush()
os.fsync(f.fileno())
os.replace(tmpFile, myFile) # os.rename pre-3.3, but os.rename won't work on Windows
rename
仅在同一文件系统内是原子的,因此最简单的方法是在 myFile
目录中创建 tmpFile
。 - darkkos.fsync
是必要的,但对于仅关注进程被中断的情况来说,这已经过度了。 - R Samuel Klatchkoos.fsync
可能非常缓慢,因为它必须等待内核刷新其缓冲区。如果有人使用此代码来写入多个文件,则肯定会导致可测量的减速。 - R Samuel Klatchkofsync(opendir(filename))
以确保重命名也写入磁盘。这不会影响此修改的原子性,只会影响此操作与另一个文件上的前一个/后一个操作的相对顺序。 - Dima Tisnek使用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)
os.rename
失败时回退到shutil.move
。这使得它可以跨越文件系统边界工作。 - Nils Wernershutils.move()
不是原子操作,因为连续调用了shutils.copy2()
和shutils.remove()
。新的实现(请参见编辑)将在当前目录中创建文件,并更好地处理异常。 - Nils Wernerdir=
,所以它将其放在临时目录中。你需要传递 dir=os.path.dirname(path_to_orig_file)
,将其放在与原始文件相同的目录中,从而允许在同一文件系统中进行原子重命名。 - ShadowRanger由于细节很容易出错,我建议使用一个小型库来处理。库的优点是它会处理所有这些琐碎的细节,并且正在被社区 审查和改进。
其中一个这样的库是python-atomicwrites
,由untitaker编写,甚至具有适当的Windows支持:
注意事项(截至2023年):
本库目前处于未维护状态。作者的评论:
[...], 我认为现在是废弃这个包的好时机。 Python 3已经有了os.replace和os.rename,对于大多数用例来说,它们可能足够好了。
原始建议:
从README中获取:
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
我正在使用以下代码来原子性地替换/写入文件:
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!\n")
这是基于这个示例的。
完成后只需链接文件:
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)
这个页面上的答案相当旧了,现在有一些库可以帮助你完成这个任务。
特别是 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上了,您可以使用 pip install --user safer
命令进行安装,或者在https://github.com/rec/safer获取最新版本。
Windows的原子解决方案,用于循环文件夹和重命名文件。已测试,原子化自动化,您可以增加概率以最小化风险,避免出现相同的文件名。您可以使用随机.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()