9

私は、ファイルに多くのものを書き込む長時間実行プロセスを持っています。結果はすべてかゼロになるはずなので、一時ファイルに書き込んで、最後に本名に名前を変更します。現在、私のコードは次のようになっています。

filename = 'whatever'
tmpname = 'whatever' + str(time.time())

with open(tmpname, 'wb') as fp:
    fp.write(stuff)
    fp.write(more stuff)

if os.path.exists(filename):
    os.unlink(filename)
os.rename(tmpname, filename)

私はいくつかの理由でそれに満足していません:

  • 例外が発生した場合、適切にクリーンアップされません
  • 並行性の問題を無視します
  • 再利用できません(プログラムのさまざまな場所でこれが必要です)

コードを改善する方法について何か提案はありますか?私を助けることができる図書館はありますか?

4

4 に答える 4

11

Pythonのtempfileモジュールを使用して、一時ファイル名を付けることができます。time.time()複数のスレッドで同時に使用された場合に同じ名前を返す可能性のある一時ファイルを使用して作成するのではなく、スレッドセーフな方法で一時ファイルを作成できます。

あなたの質問へのコメントで示唆されているように、これはコンテキストマネージャーの使用と組み合わせることができます。Pythonのソースを見ると、やりたいことを実装する方法についていくつかのアイデアを得ることができtempfile.pyます。

次のコードスニペットは、あなたが望むことをするかもしれません。から返されたオブジェクトの内部の一部を使用しますtempfile

  • 一時ファイルの作成はスレッドセーフです。
  • 正常に完了したときのファイルの名前変更は、少なくともLinuxではアトミックです。os.path.exists()との間にはos.rename()、競合状態を引き起こす可能性のある個別のチェックはありません。Linuxでのアトミックな名前変更の場合、ソースと宛先は同じファイルシステム上にある必要があります。そのため、このコードは一時ファイルを宛先ファイルと同じディレクトリに配置します。
  • RenamedTemporaryFileクラスはNamedTemporaryFile、コンテキストマネージャーを使用して閉じられた場合を除いて、ほとんどの目的でのように動作する必要があり、ファイルの名前が変更されます。

サンプル:

import tempfile
import os

class RenamedTemporaryFile(object):
    """
    A temporary file object which will be renamed to the specified
    path on exit.
    """
    def __init__(self, final_path, **kwargs):
        tmpfile_dir = kwargs.pop('dir', None)

        # Put temporary file in the same directory as the location for the
        # final file so that an atomic move into place can occur.

        if tmpfile_dir is None:
            tmpfile_dir = os.path.dirname(final_path)

        self.tmpfile = tempfile.NamedTemporaryFile(dir=tmpfile_dir, **kwargs)
        self.final_path = final_path

    def __getattr__(self, attr):
        """
        Delegate attribute access to the underlying temporary file object.
        """
        return getattr(self.tmpfile, attr)

    def __enter__(self):
        self.tmpfile.__enter__()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.tmpfile.delete = False
            result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)
            os.rename(self.tmpfile.name, self.final_path)
        else:
            result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)

        return result

その後、次のように使用できます。

with RenamedTemporaryFile('whatever') as f:
    f.write('stuff')

書き込み中、内容は一時ファイルに移動し、終了時にファイルの名前が変更されます。このコードはおそらくいくつかの調整が必要ですが、一般的な考え方はあなたが始めるのに役立つはずです。

于 2012-08-17T14:40:14.153 に答える
5

すべてまたは何もファイルに確実に書き込むには:

import os
from contextlib import contextmanager
from tempfile   import NamedTemporaryFile

if not hasattr(os, 'replace'):
    os.replace = os.rename #NOTE: it won't work for existing files on Windows

@contextmanager
def FaultTolerantFile(name):
    dirpath, filename = os.path.split(name)
    # use the same dir for os.rename() to work
    with NamedTemporaryFile(dir=dirpath, prefix=filename, suffix='.tmp') as f:
        yield f
        f.flush()   # libc -> OS
        os.fsync(f) # OS -> disc (note: on OSX it is not enough)
        f.delete = False # don't delete tmp file if `replace()` fails
        f.close()
        os.replace(f.name, name)

fsync()なしのrename()は安全ですか?も参照してください。(@Mihai Stanによる言及)

使用法

with FaultTolerantFile('very_important_file') as file:
    file.write('either all ')
    file.write('or nothing is written')

ミッシングを実装するには、Windowsで(win32fileまたはctypesモジュールを介して)os.replace()呼び出すことができます。MoveFileExW(src, dst, MOVEFILE_REPLACE_EXISTING)

複数のスレッドの場合queue.put(data)、異なるスレッドから呼び出して、専用のスレッドでファイルに書き込むことができます。

 for data in iter(queue.get, None):
     file.write(data)

queue.put(None)ループを解除します。

別の方法として、ロック(スレッド、マルチプロセッシング、ファイルロック)を使用してアクセスを同期することができます。

def write(self, data):
    with self.lock:
        self.file.write(data)
于 2012-08-17T20:34:03.243 に答える
2

このwith構成は、終了時のクリーンアップには役立ちますが、必要なコミット/ロールバックシステムには役立ちません。そのためにtry/except/elseブロックを使用できます。

また、一時ファイル名を作成するための標準的な方法を使用する必要があります。たとえば、tempfileモジュールを使用します。

名前を変更する前にfsyncすることを忘れないでください

以下は完全に変更されたコードです:

import time, os, tempfile

def begin_file(filepath):
    (filedir, filename) = os.path.split(filepath)
    tmpfilepath = tempfile.mktemp(prefix=filename+'_', dir=filedir)
    return open(os.path.join(filedir, tmpfilepath), 'wb') 

def commit_file(f):
    tmppath = f.name
    (filedir, tmpname) = os.path.split(tmppath)
    origpath = os.path.join(filedir,tmpname.split('_')[0])

    os.fsync(f.fileno())
    f.close()

    if os.path.exists(origpath):
        os.unlink(origpath)
    os.rename(tmppath, origpath)

def rollback_file(f):
    tmppath = f.name
    f.close()
    os.unlink(tmppath)


fp = begin_file('whatever')
try:
    fp.write('stuff')
except:
    rollback_file(fp)
    raise
else:
    commit_file(fp)
于 2012-08-17T14:47:16.840 に答える
1

ロックファイルモジュールを使用して、書き込み中にファイルをロックできます。それ以降のロックの試みは、前のプロセス/スレッドからのロックが解放されるまでブロックされます。

from lockfile import FileLock
with FileLock(filename):
    #open your file here....

このようにして、同時実行の問題を回避し、例外が発生した場合に残りのファイルをクリーンアップする必要がありません。

于 2012-08-17T10:31:37.460 に答える