49

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

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

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

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

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

4

7 に答える 7

27

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

幸いなことに、Python クックブックには、プラットフォームに適した方法を使用した移植可能な実装 ( portalocker ) があります。

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

portalocker.lock(file, flags)

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

于 2008-10-09T09:00:09.717 に答える
12

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

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

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

# mount -o remount,mand /dev/hdXY

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

# chmod g-x,g+s yourfile

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

fcntl.flock(fd, fcntl.LOCK_EX)

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

于 2008-10-12T02:46:56.523 に答える
7

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

https://github.com/drandreaskrueger/lockbydir

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

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


これは私の最初の質問でした:

私は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 が存在し、ファイルである場合、ユーザーが許可を持っていれば、サイレントに置き換えられます。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 する方法は? そして、なぜ私はできないのですか?

于 2015-02-15T23:41:16.677 に答える
4

これは、別個のロック機構を必要としない、移植可能な実装の半分の win32 の開始です。

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

前述したように、必要に応じて、標準の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
于 2008-10-09T19:30:33.970 に答える
2

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

新しいプロセス/タスクを開始する前に、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!

ここから収集された回避策は次のとおりです。

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 おそらく、ディレクトリベースのソリューションで十分であり、代替方法を提供するだけです。

于 2014-01-29T22:45:22.670 に答える
0

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」モードで開くと、読み取りモードであっても、必要に応じて開くことができなくなります...

于 2008-10-09T07:48:44.493 に答える