用Python原子写入文件

hoj*_*oju 52 python file-io atomic

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

open(file, 'w').write(text)
Run Code Online (Sandbox Code Playgroud)

如果脚本被中断,那么文件写入没有完成我想要没有文件而不是部分完整的文件.可以这样做吗?

Anu*_*yal 93

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

f = open(tmpFile, 'w')
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()) 
f.close()

os.rename(tmpFile, myFile)
Run Code Online (Sandbox Code Playgroud)

根据文档http://docs.python.org/library/os.html#os.rename

如果成功,重命名将是原子操作(这是POSIX要求).在Windows上,如果dst已经存在,即使它是文件,也会引发OSError; 当dst命名现有文件时,可能无法实现原子重命名

如果src和dst在不同的文件系统上,则某些Unix风格的操作可能会失败.

注意:

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

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

  • 而对于更多的完整性:`rename`仅在POSIX上的同一个文件系统是不可分割的,因此最简单的方法是在myFile`的`的目录下创建`tmpFile`. (8认同)
  • 为了完整起见,[tempfile](http://docs.python.org/library/tempfile.html)模块提供了一种简单,安全的方法来创建临时文件. (7认同)
  • +1:这是保证"原子"文件写入的唯一方法. (6认同)
  • 但你可能想在`f.close()`之前添加`os.fsync(f)`,因为这样可以确保新文件的数据实际上在磁盘上 (4认同)
  • 如果您担心操作系统突然关闭(例如断电或内核恐慌),则“os.fsync”是必要的,但对于您只是担心进程被中断的情况来说,“os.fsync”就显得有些过分了。 (2认同)
  • @JFSebastian请注意,sqlite添加了这个`fsync(opendir(filename))`以确保重命名也写入磁盘.这不会影响此修改的原子性,只会影响此操作与不同文件上的prev/next的相对顺序. (2认同)

Nil*_*ner 18

一个使用Python实现原子写入的简单代码片段tempfile.

with open_atomic('test.txt', 'w') as f:
    f.write("huzza")
Run Code Online (Sandbox Code Playgroud)

甚至可以在同一个文件中读写:

with open('test.txt', 'r') as src:
    with open_atomic('test.txt', 'w') as dst:
        for line in src:
            dst.write(line)
Run Code Online (Sandbox Code Playgroud)

使用两个简单的上下文管理

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.get('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)
Run Code Online (Sandbox Code Playgroud)

  • 运行它时似乎可以工作,但是shutil.move 使用非原子的copy2。如果 copy2 想要原子化,则需要在与目标文件相同的文件系统中创建一个临时文件。因此,回退到shutil.move 的修复只能掩盖问题。这就是为什么大多数片段将临时文件与目标文件放在同一目录中的原因。这也可以使用 tempfile.NamedTemporaryFile 使用 dir 命名参数。由于在不可写的目录中移动文件无论如何都不起作用,这似乎是最简单和最强大的解决方案。 (2认同)

vog*_*vog 8

由于很容易弄乱细节,我建议为此使用一个小型库。图书馆的优势在于它会处理所有这些细微的细节,并由社区进行审查和改进

untitaker就是其中一个此类库python-atomicwrites,它甚至具有适当的Windows支持:

从自述文件:

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.
Run Code Online (Sandbox Code Playgroud)


小智 7

有一个简单的AtomicFile帮助程序:https://github.com/sashka/atomicfile


Jak*_*tka 5

我正在使用此代码以原子方式替换/写入文件:

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
Run Code Online (Sandbox Code Playgroud)

用法:

with atomic_write('path/to/file') as f:
    f.write("allons-y!\n")
Run Code Online (Sandbox Code Playgroud)

它基于这个配方.

  • 我认为像这样的tmppath会更好'。{filepath}〜{random}'如果两个进程做同样的事情,这可以避免争用条件。这不能解决竞争问题,但是至少您不会得到包含两个进程内容的文件。 (2认同)