2010-02-25 17 views
36

estoy usando Python para escribir trozos de texto a archivos en una sola operación:escritura atómica para presentar con Python

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

Si se interrumpe la secuencia de comandos para un archivo de escritura no se completa Quiero tener ningún archivo en lugar de un archivo parcialmente completo. Se puede hacer esto?

+0

relacionados con:.. [Y threadsafe escritura de archivos tolerante a errores] (http://stackoverflow.com/questions/12003805/threadsafe-and-fault-tolerant-file-writes) – jfs

Respuesta

74

Escribir datos a un archivo temporal y cuando los datos se han escrito correctamente, cambie el nombre del archivo para el 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 doc http://docs.python.org/library/os.html#os.rename

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

también

la operación puede fallar en algunas versiones de Unix si src y dst están en diferentes sistemas de ficheros.

Nota:

  • Puede que no sea la operación atómica si localizaciones src y dest no están en el mismo sistema de archivos

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

+5

+1: Esta es la única manera de garantizar escrituras de archivo "atómicas". –

+0

perfecto - Gracias – hoju

+3

, pero es posible que desee agregar 'os.fsync (f)' antes de 'f.close()', ya que eso asegurará que los datos del nuevo archivo estén realmente en el disco –

6

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

+0

El objeto devuelto no implementa todos los métodos que tienen los objetos de archivo, por lo que no se trata de un reemplazo directo para 'abrir'. –

4

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

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 this recipe.

+1

el bucle while es atrevido, podría ser que 2 procesos simultáneos abran el mismo archivo. tempfile.NamedTemporaryFile puede superar esto. – Mic92

+1

Creo que tmppath como este sería mejor '. {Filepath} ~ {random}' esto evita las condiciones de carrera si dos procesos hacen lo mismo. Esto no resuelve la condición de carrera, pero al menos no obtienes un archivo con contenido de dos procesos. – guettli

10

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 la lectura y la escritura hacia y desde el mismo archivo:

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

utilizando 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) 
+0

El archivo temporal debe estar en el mismo sistema de archivos que el archivo que se reemplazará. Este código no funcionará de manera confiable en sistemas con múltiples sistemas de archivos. La invocación de NamedTemporaryFile necesita un dir = parámetro. – textshell

+0

Gracias por el comentario, recientemente cambié este fragmento para volver a 'shutil.move' en caso de fallo de 'os.rename'. Esto le permite trabajar a través de los límites del FS. –

+0

Parece que funciona al ejecutarlo, pero shutil.move usa copy2 que no es atómico. Y si copy2 quería ser atómico, necesitaría crear un archivo temporal en el mismo sistema de archivos que el archivo de destino. Entonces, la solución para volver a shutil.move enmascara el problema solamente. Es por eso que la mayoría de los fragmentos colocan el archivo temporal en el mismo directorio que el archivo de destino. Lo cual también es posible usando tempfile.NamedTemporaryFile usando el argumento denominado dir. Como mover un archivo en un directorio que no se puede escribir no funciona de todos modos que parece ser la solución más simple y más robusta. – textshell

2

Puesto que es muy fácil meter la pata 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 reviewed and improved por una comunidad.

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

Desde el 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. 
-1

solución Atómica para Windows de bucle carpeta y cambiar el nombre de los archivos. Probado, atómico para automatizar, puede aumentar la probabilidad de minimizar el riesgo, no el evento de tener el mismo nombre de archivo. Usted biblioteca aleatoria de símbolos combinaciones de letras y el uso del método random.choice, por str dígitos (random.random.range (50,999999999,2) Puede variar gama 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() 
Cuestiones relacionadas