web-dev-qa-db-ja.com

Python

私はPython=を使用して、単一の操作でテキストのチャンクをファイルに書き込みます:

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

スクリプトが中断されてファイルの書き込みが完了しない場合、部分的に完了したファイルではなく、ファイルを作成したくない。これはできますか?

56
hoju

データを一時ファイルに書き込み、データが正常に書き込まれたら、ファイルの名前を正しい宛先ファイルに変更します。

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)

ドキュメントによると http://docs.python.org/library/os.html#os.rename

成功した場合、名前の変更はアトミック操作になります(これはPOSIXの要件です)。 Windowsでは、dstがすでに存在する場合、ファイルであってもOSErrorが発生します。 dstが既存のファイルに名前を付けるときに、アトミックな名前変更を実装する方法がない場合があります

また

Srcとdstが異なるファイルシステムにある場合、一部のUnixフレーバーでは操作が失敗することがあります。

注意:

  • Srcとdestの場所が同じファイルシステム上にない場合、アトミック操作ではない可能性があります

  • os.fsync電源障害、システムクラッシュなどのケースでデータの整合性よりもパフォーマンス/応答性が重要な場合は、手順を省略できます

88
Anurag Uniyal

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)

2つの単純なコンテキストマネージャを使用する

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)
17
Nils Werner

簡単なAtomicFileヘルパーがあります: https://github.com/sashka/atomicfile

6

このコードを使用して、ファイルをアトミックに置換/書き込みします。

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

このレシピ に基づいています。

5
Jakub Jirutka

詳細をいじるのは非常に簡単なので、小さなライブラリを使用することをお勧めします。ライブラリの利点は、これらの重要な詳細すべてを処理し、コミュニティによって レビューおよび改善 されていることです。

そのようなライブラリの1つはpython-atomicwrites byuntitakerこれは適切なWindowsサポートさえも持っています:

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.
5
vog