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.
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.")
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.
Je préfère lockfile - Verrouillage de fichier indépendant de la plate-forme
Le verrouillage dépend de la plate-forme et du périphérique, mais vous avez généralement plusieurs options:
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.
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".
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.
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:
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"
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
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.
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.
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