web-dev-qa-db-ja.com

Pythonでファイルに排他的にアクセスするための最良の方法は何ですか?

これを解決する最もエレガントな方法は何ですか?

  • 読み取り用にファイルを開くが、まだ書き込み用に開かれていない場合のみ
  • ファイルを書き込み用に開くが、読み取りまたは書き込み用にまだ開いていない場合のみ

組み込み関数は次のように機能します

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

ソリューションは、同じプロセス内で(上記の例のように)、別のプロセスがファイルを開いたときにも機能するはずです。
クラッシュするプログラムがロックを開いたままにしない場合は、それをお勧めします。

43
mar10

完全にクロスプラットフォームの方法があるとは思いません。 UNIXでは、fcntlモジュールがこれを行います。ただし、Windowsでは(パスのそばにいると思います)、win32fileモジュールを使用する必要があります。

幸い、python Cookbookでプラットフォームに適したメソッドを使用して、移植可能な実装( portalocker )があります。

これを使用するには、ファイルを開いてから、次を呼び出します。

portalocker.lock(file, flags)

ここで、フラグはportalocker.LOCK_EXで排他的な書き込みアクセス、またはLOCK_SHで共有の読み取りアクセスです。

25
Brian

ソリューションは、同じプロセス内で(上記の例のように)、別のプロセスがファイルを開いたときにも機能するはずです。

「別のプロセス」が「どのようなプロセス」(つまり、プログラムではない)を意味する場合、Linuxでは、システムコールのみに依存してこれを達成する方法はありません(fcntl&友達)。あなたが望むのは 必須のロック であり、それを取得するLinuxの方法はもう少し複雑です:

mandオプションを使用して、ファイルを含むパーティションを再マウントします。

_# mount -o remount,mand /dev/hdXY_

ファイルにsgidフラグを設定します。

_# chmod g-x,g+s yourfile_

Pythonコードで、そのファイルの排他ロックを取得します。

fcntl.flock(fd, fcntl.LOCK_EX)

これで、catでも、ロックを解除するまでファイルを読み取ることができなくなります。

11

編集:私はそれを自分で解決しました!を使用してディレクトリの存在&ロックメカニズムとしての年齢!ファイルによるロックはWindowsでのみ安全です(Linuxが警告なしに上書きするため)。ただし、ディレクトリによるロックはLinuxとWindowsの両方で完全に機能します。そのために使いやすいクラス 'lockbydir.DLock'を作成した私のGITを参照してください。

https://github.com/drandreaskrueger/lockbydir

Readmeの下部に3つのGITplayerがあり、コード例がブラウザーでライブで実行されているのを確認できます。かっこいいですね。 :-)

ご清聴ありがとうございました


これは私の元の質問でした:

パリティ3( https://meta.stackoverflow.com/users/1454536/parity )に回答したいのですが、直接コメントすることはできません(「コメントするには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が存在し、それがファイルの場合、ユーザーに権限があればそれは通知なしで置き換えられます。 srcとdstが異なるファイルシステムにある場合、一部のUnixフレーバーでは操作が失敗する場合があります。成功した場合、名前の変更はアトミック操作になります(これはPOSIXの要件です)。 Windowsでは、dstがすでに存在する場合、OSErrorが発生します( https://docs.python.org/2/library/os.html#os.rename

サイレント置換が問題です。 Linuxの場合。 「dstがすでに存在する場合、OSErrorが発生します」は、私の目的に最適です。しかし残念ながらWindowsでのみです。

彼のif条件のため、parity3の例はほとんどの場合まだ機能していると思います

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

しかし、それからすべてがもうアトミックではありません。

2つの並列プロセスでif条件がtrueになる可能性があるため、両方の名前が変更されますが、名前の変更の競合に勝つのは1つだけです。 (Linuxでは)例外は発生しません。

助言がありますか?ありがとう!

PS:私はこれが適切な方法ではないことを知っていますが、代替手段が欠けています。私の評判を下げることで私を罰しないでください。私はこれを自分で解決するために、たくさん見回しました。 PM users in here?andmehどうして私はできないのですか?

3
akrueger

これは、別個のロックメカニズムを必要としない、移植可能な実装のwin32半分の始まりです。

Python for Windows Extensions がwin32 APIに到達する必要がありますが、これはWindowsのpythonの場合)に必須であり、代わりに ctypes を使用して実行します。必要に応じて、コードを適合させて、より多くの機能を公開できます(共有をまったく行わずにFILE_SHARE_READを許可するなど)。MSDNドキュメントも参照してください。 CreateFile および WriteFile システムコール、および ファイルの作成とオープンに関する記事

すでに述べたように、必要に応じて、標準の fcntl モジュールを使用して、UNIXの半分を実装できます。

import winerror, pywintypes, win32file

class LockError(StandardError):
    pass

class WriteLockedFile(object):
    """
    Using win32 api to achieve something similar to file(path, 'wb')
    Could be adapted to handle other modes as well.
    """
    def __init__(self, path):
        try:
            self._handle = win32file.CreateFile(
                path,
                win32file.GENERIC_WRITE,
                0,
                None,
                win32file.OPEN_ALWAYS,
                win32file.FILE_ATTRIBUTE_NORMAL,
                None)
        except pywintypes.error, e:
            if e[0] == winerror.ERROR_SHARING_VIOLATION:
                raise LockError(e[2])
            raise
    def close(self):
        self._handle.close()
    def write(self, str):
        win32file.WriteFile(self._handle, str)

上記の例の動作は次のとおりです。

>>> path = "C:\\scr.txt"
>>> file1 = WriteLockedFile(path)
>>> file2 = WriteLockedFile(path) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
    ...
LockError: ...
>>> file1.write("111")
>>> file1.close()
>>> print file(path).read()
111
3
gz.

Pythonインタープリターであり、基礎となるosとファイルシステムがos.renameをアトミック操作として扱い、宛先が存在する場合はエラーになると仮定すると、次のメソッドには競合状態がありません。私はこれはLinuxマシンの本番環境で使用します。サードパーティのライブラリは必要なく、OSに依存しません。追加のファイル作成を除いて、パフォーマンスヒットは多くのユースケースで許容されます。Pythonの関数デコレータパターンまたは「with_statement」を簡単に適用できますここでcontextmanagerを使用して、混乱を抽象化します。

新しいプロセス/タスクを開始する前に、lock_filenameが存在しないことを確認する必要があります。

import os,time
def get_tmp_file():
    filename='tmp_%s_%s'%(os.getpid(),time.time())
    open(filename).close()
    return filename

def do_exclusive_work():
    print 'exclusive work being done...'

num_tries=10
wait_time=10
lock_filename='filename.lock'
acquired=False
for try_num in xrange(num_tries):
    tmp_filename=get_tmp_file()
    if not os.path.exists(lock_filename):
        try:
            os.rename(tmp_filename,lock_filename)
            acquired=True
        except (OSError,ValueError,IOError), e:
            pass
    if acquired:
        try:
            do_exclusive_work()
        finally:
            os.remove(lock_filename)
        break
    os.remove(tmp_filename)
    time.sleep(wait_time)
assert acquired, 'maximum tries reached, failed to acquire lock file'

[〜#〜]編集[〜#〜]

Os.renameがWindows以外のOSの宛先を静かに上書きすることが明らかになりました。これを@ akruegerに指摘してくれてありがとう!

here から収集した回避策を次に示します。

Os.renameを使用する代わりに、以下を使用できます。

try:
    if os.name != 'nt': # non-windows needs a create-exclusive operation
        fd = os.open(lock_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
        os.close(fd)
    # non-windows os.rename will overwrite lock_filename silently.
    # We leave this call in here just so the tmp file is deleted but it could be refactored so the tmp file is never even generated for a non-windows OS
    os.rename(tmp_filename,lock_filename)
    acquired=True
except (OSError,ValueError,IOError), e:
    if os.name != 'nt' and not 'File exists' in str(e): raise

@ akruegerディレクトリベースのソリューションでおそらく問題はなく、代わりの方法を提供するだけです。

2
parity3

私は filelock 、クロスプラットフォームPythonライブラリで、追加のコードをほとんど必要としないライブラリを使用することを好みます。使用方法の例を次に示します:

from filelock import FileLock

lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
    file = open(path, "w")
    file.write("111")
    file.close()

with lock:ブロック内のコードはスレッドセーフです。つまり、別のプロセスがファイルにアクセスする前にコードが終了します。

0
Josh Correia

1つのアプリケーション内でファイルを開くときに安全を確保するには、次のような方法を試してください。

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') # can't open it for writing now
exclfile.FileNotExclusiveException: /tmp/a.txt

最初に「w」モードで開くと、読み取りモードであっても、希望どおりに開くことができなくなります...

0
kender