如何使文件创建成为原子操作?

新手上路,请多包涵

我正在使用 Python 在一次操作中将文本块写入文件:

 open(file, 'w').write(text)

如果脚本被中断所以文件写入没有完成我想要没有文件而不是部分完整的文件。这可以做到吗?

原文由 hoju 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 690
2 个回答

将数据写入临时文件,当数据成功写入后,将文件重命名为正确的目标文件,例如

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 存在并且是一个文件,如果用户有权限,它将被静默替换。如果 srcdst 在不同的文件系统上,操作可能会失败。如果成功,重命名将是一个原子操作(这是 POSIX 要求)。

笔记:

  • 如果 src 和 dest 位置不在同一个文件系统上,它可能不是原子操作

  • os.fsync 如果在电源故障、系统崩溃等情况下性能/响应比数据完整性更重要,则可以跳过该步骤

原文由 Anurag Uniyal 发布,翻译遵循 CC BY-SA 4.0 许可协议

一个使用 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)

原文由 Nils Werner 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题