Я использую Python для записи фрагментов текста в файлы за одну операцию:
open(file, 'w').write(text)
Если script прерван, поэтому запись файла не завершена. Я хочу, чтобы у меня не было файла, а не частично полного файла. Это можно сделать?
Я использую Python для записи фрагментов текста в файлы за одну операцию:
open(file, 'w').write(text)
Если script прерван, поэтому запись файла не завершена. Я хочу, чтобы у меня не было файла, а не частично полного файла. Это можно сделать?
Запись данных во временный файл и когда данные были успешно записаны, переименуйте файл в правильный файл назначения, например
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 называет существующий файл
и
Операция может завершиться неудачей при некоторых вариантах Unix, если src и dst находятся в разных файловых системах.
Примечание:
Это может быть не атомная операция, если местоположения src и dest не находятся в одной файловой системе.
os.fsync
шаг может быть пропущен, если производительность/отзывчивость важнее целостности данных в таких случаях, как сбой питания, сбой системы и т.д.
Простой фрагмент, который реализует атомарную запись с использованием 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:
f.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)
Существует простой помощник AtomicFile: https://github.com/sashka/atomicfile
Im, используя этот код для автоматической замены/записи файла:
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-atomicwrites
от unitaker, которая даже имеет правильную поддержку 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.