атомарная запись в файл с Python - PullRequest
46 голосов
/ 25 февраля 2010

Я использую Python для записи кусков текста в файлы за одну операцию:

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

Если сценарий прерван и запись файла не завершена, я хочу, чтобы файл не был частично, а частично завершен. Можно ли это сделать?

Ответы [ 6 ]

84 голосов
/ 25 февраля 2010

Записать данные во временный файл и, когда данные были успешно записаны, переименовать файл в правильный файл назначения, например,

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 шаг может быть пропущен, если производительность / скорость реагирования важнее, чем целостность данных в таких случаях, как сбой питания, сбой системы и т. Д.

16 голосов
/ 07 апреля 2015

Простой фрагмент, который реализует атомарную запись с использованием 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)
6 голосов
/ 25 июля 2012

Существует простой помощник AtomicFile: https://github.com/sashka/atomicfile

5 голосов
/ 28 июня 2016

Поскольку детали легко запутать, я рекомендую использовать для этого небольшую библиотеку. Преимущество библиотеки состоит в том, что она заботится обо всех этих мельчайших деталях и в настоящее время проверяется и улучшается сообществом.

Одна из таких библиотек - python-atomicwrites от untitaker , которая даже имеет соответствующую поддержку 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 голосов
/ 30 августа 2014

Я использую этот код для атомарной замены / записи файла:

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

Он основан на этом рецепте .

0 голосов
/ 04 февраля 2018

Атомное решение для Windows для зацикливания папок и переименования файлов. Протестировано, атомарно, чтобы автоматизировать, вы можете увеличить вероятность, чтобы минимизировать риск, не связанный с тем же именем файла. В случайной библиотеке комбинаций буквенных символов используется метод random.choice, для цифры str (random.random.range (50,999999999,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()
...