web-dev-qa-db-fra.com

Verrouiller un fichier en Python

J'ai besoin de verrouiller un fichier pour l'écrire en Python. Il sera accessible à partir de plusieurs processus Python à la fois. J'ai trouvé des solutions en ligne, mais la plupart échouent car elles sont souvent uniquement basées sur Unix ou Windows.

119
Evan Fosmark

Bon, alors j'ai fini par aller avec le code que j'ai écrit ici, sur mon site web le lien est mort, vue sur archive.org ( également disponible sur GitHub ). Je peux l'utiliser de la manière suivante:

from filelock import FileLock

with FileLock("myfile.txt"):
    # work with the file as it is now locked
    print("Lock acquired.")
92
Evan Fosmark

Il existe un module de verrouillage de fichiers multiplate-forme ici: Portalocker

Bien que, comme le dit Kevin, écrire dans un fichier à partir de plusieurs processus à la fois est quelque chose que vous voulez éviter autant que possible.

Si vous pouvez résoudre votre problème dans une base de données, vous pouvez utiliser SQLite. Il prend en charge les accès simultanés et gère son propre verrouillage.

36
John Fouhy

Je préfère lockfile - Verrouillage de fichier indépendant de la plate-forme

15
ferrdo

Le verrouillage dépend de la plate-forme et du périphérique, mais vous avez généralement plusieurs options:

  1. Utilisez flock () ou équivalent (si votre système d’exploitation le supporte). Ceci est un verrouillage consultatif, à moins que vous ne vérifiiez le verrouillage, il est ignoré.
  2. Utilisez une méthode lock-copy-move-unlock dans laquelle vous copiez le fichier, écrivez les nouvelles données, puis les déplacez (déplacez-les (ne pas copiez - déplacer est une opération atomique sous Linux: vérifiez votre existence du fichier de verrouillage.
  3. Utilisez un répertoire comme "verrou". Cela est nécessaire si vous écrivez sur NFS, car NFS ne prend pas en charge flock ().
  4. Il existe également la possibilité d'utiliser la mémoire partagée entre les processus, mais je n'ai jamais essayé cela; c'est très spécifique au système d'exploitation.

Pour toutes ces méthodes, vous devrez utiliser une technique d’essai et de test du verrou (réessayer après une erreur). Cela laisse une petite fenêtre pour une mauvaise synchronisation, mais il est généralement assez petit pour ne pas être un problème majeur.

Si vous recherchez une solution multi-plateforme, il est préférable de vous connecter à un autre système via un autre mécanisme (la meilleure solution est la technique NFS ci-dessus). 

Notez que sqlite est soumis aux mêmes contraintes sur NFS que les fichiers normaux. Par conséquent, vous ne pouvez pas écrire dans une base de données sqlite sur un partage réseau et bénéficier de la synchronisation gratuitement.

12
Richard Levasseur

Les autres solutions citent beaucoup de bases de code externes. Si vous préférez le faire vous-même, voici du code pour une solution multiplate-forme qui utilise les outils de verrouillage de fichiers respectifs sur les systèmes Linux/DOS.

try:
    # Posix based file locking (Linux, Ubuntu, MacOS, etc.)
    import fcntl, os
    def lock_file(f):
        fcntl.lockf(f, fcntl.LOCK_EX)
    def unlock_file(f):
        fcntl.lockf(f, fcntl.LOCK_UN)
except ModuleNotFoundError:
    # Windows file locking
    import msvcrt, os
    def file_size(f):
        return os.path.getsize( os.path.realpath(f.name) )
    def lock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f))
    def unlock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f))


# Class for ensuring that all file operations are atomic, treat
# initialization like a standard call to 'open' that happens to be atomic.
# This file opener *must* be used in a "with" block.
class AtomicOpen:
    # Open the file with arguments provided by user. Then acquire
    # a lock on that file object (WARNING: Advisory locking).
    def __init__(self, path, *args, **kwargs):
        # Open the file and acquire a lock on the file before operating
        self.file = open(path,*args, **kwargs)
        # Lock the opened file
        lock_file(self.file)

    # Return the opened file object (knowing a lock has been obtained).
    def __enter__(self, *args, **kwargs): return self.file

    # Unlock the file and close the file object.
    def __exit__(self, exc_type=None, exc_value=None, traceback=None):        
        # Flush to make sure all buffered contents are written to file.
        self.file.flush()
        os.fsync(self.file.fileno())
        # Release the lock on the file.
        unlock_file(self.file)
        self.file.close()
        # Handle exceptions that may have come up during execution, by
        # default any exceptions are raised to the user.
        if (exc_type != None): return False
        else:                  return True        

Maintenant, AtomicOpen peut être utilisé dans un bloc with où l'on utiliserait normalement une instruction open.

AVERTISSEMENT: Si l'exécution sur Windows et Python se bloque avant que exit soit appelée, je ne suis pas sûr du comportement du verrou.

ATTENTION: Le verrouillage fourni ici est indicatif, pas absolu. Tous les processus potentiellement concurrents doivent utiliser la classe "AtomicOpen".

10
Thomas Lux

La coordination de l’accès à un seul fichier au niveau du système d’exploitation comporte de nombreux problèmes que vous ne souhaitez probablement pas résoudre.

Votre meilleur pari est d’avoir un processus séparé qui coordonne l’accès en lecture/écriture à ce fichier.

7
Kevin

J'ai examiné plusieurs solutions pour y parvenir et mon choix a été oslo.concurrency

C'est puissant et relativement bien documenté. C'est basé sur des attaches.

Autres solutions:

  • Portalocker : nécessite pywin32, qui est une installation exe, donc impossible via pip
  • attaches : mal documentées
  • lockfile : obsolète
  • flufl.lock : Verrouillage de fichiers conforme à NFS pour les systèmes POSIX.
  • simpleflock : Dernière mise à jour 2013-07
  • zc.lockfile : Dernière mise à jour 2016-06 (à partir de 2017-03)
  • lock_file : Dernière mise à jour en 2007-10
7
Maxime Viargues

Verrouiller un fichier est généralement une opération spécifique à la plate-forme. Vous devrez peut-être donc prévoir une possibilité d'exécution sur différents systèmes d'exploitation. Par exemple:

import os

def my_lock(f):
    if os.name == "posix":
        # Unix or OS X specific locking here
    Elif os.name == "nt":
        # Windows specific locking here
    else:
        print "Unknown operating system, lock unavailable"
5
Greg Hewgill

Le scenario est comme ça: L'utilisateur demande à un fichier de faire quelque chose. Ensuite, si l'utilisateur envoie à nouveau la même demande, il l'informe que la deuxième demande n'est pas terminée jusqu'à la fin de la première demande. C'est pourquoi, j'utilise un mécanisme de verrouillage pour gérer ce problème.

Voici mon code de travail: 

from lockfile import LockFile
lock = LockFile(lock_file_path)
status = ""
if not lock.is_locked():
    lock.acquire()
    status = lock.path + ' is locked.'
    print status
else:
    status = lock.path + " is already locked."
    print status

return status
1
Günay Gültekin

J'ai travaillé sur une situation comme celle-ci où je lance plusieurs copies du même programme à partir du même répertoire/dossier et en consignant les erreurs. Mon approche était d'écrire un "fichier de verrouillage" sur le disque avant d'ouvrir le fichier journal. Le programme vérifie la présence du "fichier de verrouillage" avant de poursuivre et attend son tour si le "fichier de verrouillage" existe.

Voici le code:

def errlogger(error):

    while True:
        if not exists('errloglock'):
            lock = open('errloglock', 'w')
            if exists('errorlog'): log = open('errorlog', 'a')
            else: log = open('errorlog', 'w')
            log.write(str(datetime.utcnow())[0:-7] + ' ' + error + '\n')
            log.close()
            remove('errloglock')
            return
        else:
            check = stat('errloglock')
            if time() - check.st_ctime > 0.01: remove('errloglock')
            print('waiting my turn')

EDIT --- Après avoir réfléchi à quelques-uns des commentaires concernant les verrous périmés ci-dessus, j'ai modifié le code pour ajouter une vérification de la minutie du "fichier verrou". Plusieurs milliers d’itérations de cette fonction sur mon système ont donné une moyenne de 0,002066 secondes à peine d’avant:

lock = open('errloglock', 'w')

juste après:

remove('errloglock')

je me suis donc dit que je commencerais par 5 fois ce montant afin d'indiquer la stagnation et de suivre l'évolution de la situation.

De plus, en travaillant avec le timing, je me suis rendu compte que j'avais un peu de code qui n'était pas vraiment nécessaire:

lock.close()

que j’avais immédiatement après la déclaration ouverte, je l’ai donc supprimée dans cette modification.

1
whitebeard

J'ai trouvé une solution simple et efficace (!) implementation from grizzled-python.

L'utilisation simple os.open (..., O_EXCL) + os.close () ne fonctionnait pas sous Windows.

0
Speq

Vous pouvez trouver pylocker très utile. Il peut être utilisé pour verrouiller un fichier ou pour verrouiller des mécanismes en général et est accessible à partir de plusieurs processus Python simultanément 

Si vous voulez simplement verrouiller un fichier, voici comment cela fonctionne: 

import uuid
from pylocker import Locker

#  create a unique lock pass. This can be any string.
lpass = str(uuid.uuid1())

# create locker instance.
FL = Locker(filePath='myfile.txt', lockPass=lpass, mode='w')

# aquire the lock
with FL as r:
    # get the result
    acquired, code, fd  = r

    # check if aquired.
    if fd is not None:
        print fd
        fd.write("I have succesfuly aquired the lock !")

# no need to release anything or to close the file descriptor, 
# with statement takes care of that. let's print fd and verify that.
print fd
0
Cobry