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

Python 如何使文件创建成为原子操作?,python,file-io,atomic,Python,File Io,Atomic,我使用Python在单个操作中将文本块写入文件: open(file, 'w').write(text) 如果脚本被中断,因此文件写入未完成,我希望没有文件,而不是部分完成的文件。可以这样做吗?将数据写入临时文件,成功写入数据后,将文件重命名为正确的目标文件,例如 f = open(tmpFile, 'w') f.write(text) # make sure that all data is on disk # see http://stackoverflow.com/questions/7

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

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

如果脚本被中断,因此文件写入未完成,我希望没有文件,而不是部分完成的文件。可以这样做吗?

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

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)
据博士说

如果成功,重命名将是一个原子操作(这是一个 POSIX要求)。在Windows上,如果是dst 已存在,将引发操作错误 即使它是一个文件;可能没有 实现原子重命名的方法 dst命名现有文件

如果src和dst位于不同的文件系统上,则该操作可能会在某些Unix版本上失败

注:

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

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


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

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")

它基于。

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

因为细节很容易搞乱,所以我建议使用一个小型的库。图书馆的优势在于它能照顾到所有这些细节,而且是由社区提供的

其中一个库是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.
通过PIP安装:

pip install atomicwrites

Windows循环文件夹和重命名文件的原子解决方案。经过测试,原子级自动化,您可以提高概率,以最大限度地降低不使用相同文件名事件的风险。字母符号组合的随机库使用random.choice方法表示数字str(random.random.range(5099999999,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()

这个页面上的答案很旧,现在有一些库可以为您提供这些答案

特别是
safe
是一个库,旨在帮助防止程序员错误损坏文件、套接字连接或通用流。它非常灵活,除其他外,它可以选择使用内存或临时文件,甚至可以保留临时文件以防出现故障

他们的例子正是您想要的:

# dangerous
with open(filename, 'w') as fp:
    json.dump(data, fp)
    # If an exception is raised, the file is empty or partly written

它在PyPI中,只需使用
pip install--user safe
安装它,或者在

获取最新版本,但您可能需要在
f.close()之前添加
os.fsync(f)
,因为这将确保新文件的数据实际上在磁盘上完整,该模块提供了一种简单、安全的方法来创建临时文件。为了更完整:
rename
仅在POSIX上的同一文件系统中是原子的,所以最简单的方法是在
myFile
目录中创建
tmpFile
如果文件已经存在,is将无法在Windows上运行:“在Windows上,如果dst已经存在,将引发OSError”@J.F.Sebastian注意sqlite添加此
fsync(opendir(文件名))
以确保重命名也写入磁盘。这不会影响此修改的原子性,只影响此操作与不同文件上的prev/next的相对顺序。相关:while循环是快速的,可能是两个并发进程打开同一个文件。tempfile.NamedTemporaryFile可以克服这一问题。我认为像这样的tmppath最好是“{filepath}{random}”'如果两个进程执行相同的操作,这将避免争用条件。这不会解决争用条件,但至少不会得到包含两个进程内容的文件。临时文件需要与要替换的文件位于同一文件系统上。此代码在具有多个文件系统的系统上无法可靠工作。NamedTemporaryFile调用eeds a dir=parameter。感谢您的评论,我最近将此代码段改为
shutil.move
,以防
os.rename
失败。这允许它跨FS边界工作。运行它时似乎可以工作,但shutil.move使用的是不是原子的copy2。如果copy2想要成为原子的,则需要创建将临时文件放在与目标文件相同的文件系统中。因此,回退到shutil.move的修复程序只会掩盖问题。这就是为什么大多数代码段将临时文件放在与目标文件相同的目录中。也可以使用dir-named参数使用tempfile.NamedTemporaryFile不可写的ry无论如何都不能工作,这似乎是最简单和最健壮的解决方案。正确的,我假设
shutils.move()
是非原子的,因为
shutils.copy2()
shutils.remove()
连续调用。新的实现(请参见编辑)现在将改为在当前目录中创建文件,并更好地处理异常。为什么在读取和写入同一文件时这是原子的?在上面的示例中
open('test.txt',r'))as src:
用于读取文件内容。从这个意义上讲,写入是原子性的,但读取可能不同。对于像
.ini
这样的文件类型,当与configparser一起用于读取操作时,使用装饰程序播放。不确定此示例是否完全证明了从同一文件读取超过200000个线程的原子性。这将抛出太多打开的文件错误。下面是安装它的命令:
pip install atomicwrites==1.4.0
@neves谢谢你的提示。我相应地改进了我的答案。但是,我在最后删除了显式版本号,因为这将是一个危险的建议,尤其是在一个已经存在多年且几乎确定的答案中y将继续存在数年
# safer
with safer.open(filename, 'w') as fp:
    json.dump(data, fp)
    # If an exception is raised, the file is unchanged.