0

Как лучше открыть файл для эксклюзивного доступа в Python?

9

Описание проблемы для StackOverflow:

Каково наиболее элегантное решение следующей задачи:

  • Открыть файл для чтения, но только если он не открыт для записи.
  • Открыть файл для записи, но только если он не открыт ни для чтения, ни для записи.

Встроенные функции работают следующим образом:

>>> path = r"c:\scr.txt"
>>> file1 = open(path, "w")
>>> print(file1)
<open file 'c:\scr.txt', mode 'w' at 0x019F88D8>
>>> file2 = open(path, "w")
>>> print(file2)
<open file 'c:\scr.txt', mode 'w' at 0x02332188>
>>> file1.write("111")
>>> file2.write("222")
>>> file1.close()

Теперь файл scr.txt содержит '111'.

>>> file2.close()

Файл scr.txt был перезаписан и теперь содержит '222' (на Windows, Python 2.4).

Решение должно работать как внутри одного процесса (согласно приведенному выше примеру), так и когда другой процесс открыл файл. Также желательно, чтобы при аварийном завершении программы блокировка не оставалась открытой.

2 ответ(ов)

0

EDIT: Я сам решил проблему! Я использовал существование директории и её возраст как механизм блокировки! Блокировка файла безопасна только на Windows (поскольку в Linux происходит тихая перезапись), но блокировка по директории работает отлично как на Linux, так и на Windows. Я создал удобный класс 'lockbydir.DLock' для этой цели, который можно найти в моем репозитории:

https://github.com/drandreaskrueger/lockbydir

Внизу README вы найдете 3 GIT-плейера, где можно увидеть живые примеры кода в вашем браузере! Довольно круто, не так ли? 😃

Спасибо за внимание!


Это был мой исходный вопрос:

Я хотел бы ответить parity3 (https://meta.stackoverflow.com/users/1454536/parity3), но не могу комментировать напрямую ('Вам нужно 50 репутации, чтобы комментировать'), и не вижу никакого способа связаться с ним/ней напрямую. Что вы мне посоветуете, чтобы донести до него информацию?

Мой вопрос:

Я реализовал что-то похожее на то, что предложил parity3 здесь в ответе: https://stackoverflow.com/a/21444311/3693375 ("Предполагая, что ваш интерпретатор Python ...")

И это отлично работает на Windows. (Я использую это для реализации механизма блокировки, работающего в параллельно запущенных процессах. https://github.com/drandreaskrueger/lockbyfile)

Но, в отличие от того, что говорит parity3, это НЕ работает так же на Linux:

os.rename(src, dst)

Переименовать файл или директорию src в dst. ... В Unix, если dst существует и является файлом, он будет заменен без уведомления, если у пользователя есть на это разрешение. Операция может завершиться неудачей на некоторых Unix системах, если src и dst находятся на разных файловых системах. Если операция прошла успешно, переименование будет атомарной операцией (это требование POSIX). На Windows, если dst уже существует, будет вызвано исключение OSError (https://docs.python.org/2/library/os.html#os.rename)

Тихая перезапись – это проблема. На Linux.
"если dst уже существует, будет вызвано исключение OSError" – это прекрасно для моих целей, но, к сожалению, только для Windows.

Я полагаю, что пример parity3 все еще работает большую часть времени из-за его условия if

if not os.path.exists(lock_filename):
    try:
        os.rename(tmp_filename, lock_filename)

Но тогда это все больше не атомарно.

Поскольку условие if может быть истинно в двух параллельных процессах, и тогда оба могут выполнить переименование, но только один выиграет в гонке переименования. И никакого исключения не будет вызвано (в Linux).

Есть ли какие-нибудь предложения? Спасибо!

P.S.: Я знаю, что это не самый правильный способ, но у меня нет альтернативы. ПОЖАЛУЙСТА, не наказывайте меня снижением репутации. Я много искал, чтобы решить это сам. Как отправить личные сообщения пользователям здесь? И пфу почему я не могу?

0

Чтобы обеспечить безопасность при открытии файлов в одном приложении, вы можете попробовать использовать следующий код:

import time

class ExclusiveFile(file):
    openFiles = {}
    fileLocks = []

    class FileNotExclusiveException(Exception):
        pass

    def __init__(self, *args):
        sMode = 'r'
        sFileName = args[0]
        try:
            sMode = args[1]
        except:
            pass

        while sFileName in ExclusiveFile.fileLocks:
            time.sleep(1)

        ExclusiveFile.fileLocks.append(sFileName)

        if not sFileName in ExclusiveFile.openFiles.keys() or (ExclusiveFile.openFiles[sFileName] == 'r' and sMode == 'r'):
            ExclusiveFile.openFiles[sFileName] = sMode
            try:
                file.__init__(self, sFileName, sMode)
            finally:
                ExclusiveFile.fileLocks.remove(sFileName)
        else:
            ExclusiveFile.fileLocks.remove(sFileName)
            raise self.FileNotExclusiveException(sFileName)

    def close(self):
        del ExclusiveFile.openFiles[self.name]
        file.close(self)

С помощью этого кода вы создаете подкласс для класса file. Теперь вы можете использовать его следующим образом:

>>> f = ExclusiveFile('/tmp/a.txt', 'r')
>>> f
<open file '/tmp/a.txt', mode 'r' at 0xb7d7cc8c>
>>> f1 = ExclusiveFile('/tmp/a.txt', 'r')
>>> f1
<open file '/tmp/a.txt', mode 'r' at 0xb7d7c814>
>>> f2 = ExclusiveFile('/tmp/a.txt', 'w')  # нельзя открыть для записи сейчас
exclfile.FileNotExclusiveException: /tmp/a.txt

Если вы сначала откроете файл в режиме 'w', то дальнейшее открытие, даже в режиме чтения, не будет разрешено, так как и задумывалось.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь