सवाल पाइथन के साथ फाइल करने के लिए परमाणु लेखन


मैं एक ही ऑपरेशन में फ़ाइलों के पाठ के टुकड़े लिखने के लिए पायथन का उपयोग कर रहा हूं:

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

यदि स्क्रिप्ट बाधित है तो फ़ाइल लिखना पूरा नहीं होता है, मैं आंशिक रूप से पूर्ण फ़ाइल की बजाय कोई फ़ाइल नहीं चाहता हूं। क्या यह किया जा सकता है?


38
2018-02-25 12:21


मूल


सम्बंधित: थ्रेडसेफ और गलती-सहनशील फ़ाइल लिखती है - jfs


जवाब:


डेटा को अस्थायी फ़ाइल में लिखें और जब डेटा सफलतापूर्वक लिखा गया हो, तो फ़ाइल को सही गंतव्य फ़ाइल में नाम दें

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 आवश्यकता)। विंडोज़ पर, अगर डीएसटी   पहले से मौजूद है, ओएसरर उठाया जाएगा   भले ही यह एक फाइल है; नहीं हो सकता है   जब परमाणु नाम लागू करने का तरीका   डीएसटी एक मौजूदा फाइल का नाम है

भी

यदि सर्वर और डीएसटी विभिन्न फाइल सिस्टम पर हैं तो ऑपरेशन कुछ यूनिक्स स्वादों पर विफल हो सकता है।

ध्यान दें:

  • यदि स्रोत और dest स्थान एक ही फाइल सिस्टम पर नहीं हैं तो यह परमाणु संचालन नहीं हो सकता है

  • os.fsync यदि बिजली विफलता, सिस्टम क्रैश इत्यादि जैसे मामलों में डेटा अखंडता की तुलना में प्रदर्शन / प्रतिक्रिया अधिक महत्वपूर्ण है तो चरण छोड़ा जा सकता है


75
2018-02-25 12:39



+1: "परमाणु" फ़ाइल लिखने की गारंटी देने का यही एकमात्र तरीका है। - S.Lott
लेकिन आप जोड़ना चाहते हैं os.fsync(f) से पहले f.close(), क्योंकि यह सुनिश्चित करेगा कि नई फ़ाइल का डेटा वास्तव में डिस्क पर है - Dan D.
पूर्णता के लिए, tempfile मॉड्यूल अस्थायी फ़ाइलों को बनाने के लिए एक आसान, सुरक्षित तरीका प्रदान करता है। - itsadok
और अधिक पूर्णता के लिए: rename POSIX पर केवल उसी फाइल सिस्टम के भीतर परमाणु है, इसलिए सबसे आसान तरीका बनाना है tmpFile की निर्देशिका में myFile। - darkk
@ जेएफ। सेबेस्टियन नोट करें कि एसक्लाइट इसे जोड़ता है fsync(opendir(filename)) यह सुनिश्चित करने के लिए कि नाम डिस्क पर भी लिखा गया है। यह इस संशोधन की परमाणुता को प्रभावित नहीं करता है, केवल इस ऑपरेशन के सापेक्ष क्रम को एक अलग फ़ाइल पर पिछला / अगला बना देता है। - Dima Tisnek


एक सरल स्निपेट जो पाइथन का उपयोग करके परमाणु लेखन लागू करता है 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)

11
2018-04-07 12:23



फ़ाइल को प्रतिस्थापित करने के लिए अस्थायी फ़ाइल को उसी फ़ाइल सिस्टम पर होना आवश्यक है। यह कोड एकाधिक फ़ाइल सिस्टम वाले सिस्टम पर भरोसेमंद काम नहीं करेगा। नामांकित समकालीन फ़ाइल आमंत्रण को एक डीआईआर = पैरामीटर की आवश्यकता होती है। - textshell
टिप्पणी के लिए धन्यवाद, मैंने हाल ही में इस स्निपेट को वापस गिरने के लिए बदल दिया है shutil.move के मामले में os.rename नाकाम रहने के। यह इसे एफएस सीमाओं में काम करने की अनुमति देता है। - Nils Werner
यह चलते समय काम करता प्रतीत होता है, लेकिन shutil.move कॉपी 2 का उपयोग करता है जो परमाणु नहीं है। और यदि कॉपी 2 परमाणु होना चाहता था तो उसे उसी फ़ाइल सिस्टम में गंतव्य फ़ाइल के रूप में एक अस्थायी फ़ाइल बनाने की आवश्यकता होगी। तो, shutil.move पर वापस गिरने के लिए फिक्स केवल समस्या का मुखौटा। यही कारण है कि अधिकांश स्निपेट अस्थायी फ़ाइल को उसी फ़ाइल में लक्षित फ़ाइल के रूप में रखते हैं। Tempfile.NamedTemporaryFile का उपयोग करके तर्क भी नामित डीआईआर का उपयोग करना भी संभव है। एक निर्देशिका में एक फ़ाइल पर जाने के रूप में जो लिखने योग्य नहीं है वैसे भी काम नहीं करता है जो सबसे सरल और सबसे मजबूत समाधान प्रतीत होता है। - textshell
सही, मैंने यह माना shutils.move() गैर-परमाणु कारण था shutils.copy2() तथा shutils.remove() उत्तराधिकार में बुलाया जाता है। नया कार्यान्वयन (संपादन देखें) अब वर्तमान निर्देशिका में फ़ाइल बनायेगा और अपवादों को बेहतर तरीके से संभाल देगा। - Nils Werner


एक साधारण परमाणु फ़ाइल सहायक है: https://github.com/sashka/atomicfile


6
2017-07-24 20:58



लौटाया गया ऑब्जेक्ट फ़ाइल ऑब्जेक्ट्स के सभी तरीकों को लागू नहीं करता है, इसलिए यह ड्रॉप-इन प्रतिस्थापन नहीं है open। - Nils Werner


मैं इस कोड का उपयोग परमाणु रूप से फ़ाइल को प्रतिस्थापित / लिखने के लिए कर रहा हूं:

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

यह पर आधारित है यह नुस्खा


4
2017-08-30 19:59



जबकि लूप रैसी है, यह एक ही फाइल खोलने वाली 2 समवर्ती प्रक्रिया हो सकती है। tempfile.NamedTemporaryFile इसे दूर कर सकते हैं। - Mic92
मुझे लगता है कि इस तरह का tmppath बेहतर होगा '। {Filepath} ~ {random}' यदि दो प्रक्रियाएं समान होती हैं तो यह दौड़ की स्थिति से बचाती है। यह दौड़ की स्थिति को हल नहीं करता है, लेकिन कम से कम आपको दो प्रक्रियाओं की सामग्री के साथ फ़ाइल नहीं मिलती है। - guettli


चूंकि विवरण के साथ गड़बड़ करना बहुत आसान है, इसलिए मैं इसके लिए एक छोटी पुस्तकालय का उपयोग करने की सलाह देता हूं। लाइब्रेरी का लाभ यह है कि यह इन सभी नट-किरकिरा विवरणों का ख्याल रखता है, और हो रहा है समीक्षा और सुधार हुआ एक समुदाय द्वारा

ऐसी एक पुस्तकालय है python-atomicwrites द्वारा untitaker यहां तक ​​कि उचित विंडोज समर्थन भी है:

रीडमे से:

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
2018-06-28 11:43





विंडोज़ लूप फ़ोल्डर और फ़ाइलों का नाम बदलने के लिए परमाणु समाधान। परीक्षण, स्वचालित करने के लिए परमाणु, आप जोखिम को कम करने के लिए संभावना को बढ़ा सकते हैं न कि फ़ाइल नाम होने की स्थिति के लिए। अक्षर प्रतीकों के संयोजन के लिए आप यादृच्छिक पुस्तकालय अंक str (random.random.range (50,999999999,2) के लिए random.choice विधि का उपयोग करते हैं। आप जितनी चाहें अंकों की श्रेणी बदल सकते हैं।

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