escritura atómica en archivo con Python


Estoy usando Python para escribir trozos de texto en archivos en una sola operación:

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

Si el script se interrumpe para que una escritura de archivo no se complete, quiero no tener ningún archivo en lugar de un archivo parcialmente completo. Se puede hacer esto?

Author: hoju, 2010-02-25

6 answers

Escriba datos en un archivo temporal y cuando los datos se hayan escrito correctamente, cambie el nombre del archivo al archivo de destino correcto, por ejemplo

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)

Según el doc http://docs.python.org/library/os.html#os.rename

Si tiene éxito, el cambio de nombre será una operación atómica (esto es un Requisito de POSIX). En Windows, si dst ya existe, OSError se elevará incluso si se trata de un archivo; no puede haber manera de implementar un cambio de nombre atómico cuando dst nombra un archivo existente

También

La operación puede fallar en algunos tipos Unix si src y dst están en sistemas de archivos diferentes.

Nota:

  • Puede no ser una operación atómica si las ubicaciones src y dest no están en el mismo sistema de archivos

  • os.fsync el paso se puede omitir si el rendimiento/la capacidad de respuesta es más importante que la integridad de los datos en casos como falla de energía, caída del sistema, etc

 76
Author: Anurag Uniyal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-28 05:43:11

Un fragmento simple que implementa la escritura atómica usando Python tempfile.

with open_atomic('test.txt', 'w') as f:
    f.write("huzza")

O incluso leyendo y escribiendo desde y hacia el mismo archivo:

with open('test.txt', 'r') as src:
    with open_atomic('test.txt', 'w') as dst:
        for line in src:
            dst.write(line)

Usando dos gestores de contexto simples

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)
 13
Author: Nils Werner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-26 08:56:43

Hay un simple ayudante AtomicFile: https://github.com/sashka/atomicfile

 6
Author: Alexander Saltanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-24 20:58:35

Estoy usando este código para reemplazar/escribir un archivo atómicamente:

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

Uso:

with atomic_write('path/to/file') as f:
    f.write("allons-y!\n")

Se basa en esta receta.

 5
Author: Jakub Jirutka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-30 20:06:51

Dado que es muy fácil meterse con los detalles, recomiendo usar una pequeña biblioteca para eso. La ventaja de una biblioteca es que cuida todos estos detalles esenciales, y está siendo revisada y mejorada por una comunidad.

Una de estas bibliotecas es python-atomicwrites por untitaker que incluso tiene soporte adecuado para Windows:

Del 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.
 4
Author: vog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-28 11:43:30

Solución atómica para Windows a la carpeta de bucle y cambiar el nombre de los archivos. Probado, atómico para automatizar, puede aumentar la probabilidad de minimizar el riesgo de no evento de tener el mismo nombre de archivo. Usted biblioteca aleatoria para combinaciones de símbolos de letras utilizar al azar.método de elección, para digit str (random.aleatorio.rango(50,999999999,2). Puede variar el rango de dígitos como desee.

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()
 -1
Author: Mindaugas Vaitkus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-04 19:46:30