Verrouiller un fichier en Python

152

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 à mes fins car elles sont souvent uniquement basées sur Unix ou Windows.

Evan Fosmark
la source

Réponses:

115

D'accord, j'ai donc fini par utiliser le code que j'ai écrit ici, sur mon site, le lien est mort, voir sur archive.org ( également disponible sur GitHub ). Je peux l'utiliser de la manière suivante:

from filelock import FileLock

with FileLock("myfile.txt.lock"):
    print("Lock acquired.")
    with open("myfile.txt"):
        # work with the file as it is now locked
Evan Fosmark
la source
10
Comme indiqué par un commentaire sur le billet de blog, cette solution n'est pas "parfaite", en ce sens qu'il est possible que le programme se termine de telle manière que le verrou reste en place et que vous devez supprimer manuellement le verrou avant le fichier redevient accessible. Cependant, cela mis à part, c'est toujours une bonne solution.
leetNightshade
3
Encore une autre version améliorée de FileLock d'Evan peut être trouvée ici: github.com/ilastik/lazyflow/blob/master/lazyflow/utility/…
Stuart Berg
3
OpenStack a publié sa propre implémentation (enfin, Skip Montanaro) - pylockfile - Très similaire à celles mentionnées dans les commentaires précédents, mais vaut toujours la peine d'y jeter un coup d'œil.
jweyrich
7
@jweyrich Openstacks pylockfile est désormais obsolète. Il est conseillé d'utiliser à la place des attaches ou oslo.concurrency .
harbun
2
Une autre implémentation similaire je suppose: github.com/benediktschmitt/py-filelock
herry
39

Il existe un module de verrouillage de fichiers multiplateforme 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 dans la mesure du possible.

Si vous parvenez à intégrer votre problème dans une base de données, vous pouvez utiliser SQLite. Il prend en charge l'accès simultané et gère son propre verrouillage.

John Fouhy
la source
16
+1 - SQLite est presque toujours la voie à suivre dans ce genre de situations.
cdleary
2
Portalocker nécessite des extensions Python pour Windows, à ce sujet.
n611x007
2
@naxa il existe une variante de celui-ci qui ne repose que sur msvcrt et ctypes, voir roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…
Shmil The Cat
@ n611x007 Portalocker vient d'être mis à jour, il ne nécessite donc plus d'extensions sur Windows :)
Wolph
2
SQLite prend en charge l'accès simultané?
piotr
23

Les autres solutions citent de nombreuses bases de code externes. Si vous préférez le faire vous-même, voici un code pour une solution multiplateforme 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, AtomicOpenpeut être utilisé dans un withbloc où l'on utiliserait normalement une openinstruction.

AVERTISSEMENT: si l'exécution sous Windows et Python se bloque avant l' appel de exit , je ne suis pas sûr du comportement de verrouillage.

AVERTISSEMENT: Le verrouillage fourni ici est indicatif et non absolu. Tous les processus potentiellement concurrents doivent utiliser la classe "AtomicOpen".

Thomas Lux
la source
unlock_filefichier sur Linux ne devrait pas appeler à fcntlnouveau avec le LOCK_UNdrapeau?
eadmaster
Le déverrouillage se produit automatiquement lorsque l'objet fichier est fermé. Cependant, c'était une mauvaise pratique de programmation de ma part de ne pas l'inclure. J'ai mis à jour le code et ajouté l'opération de déverrouillage fcntl!
Thomas Lux
En __exit__vous en closedehors de la serrure après unlock_file. Je crois que le runtime pourrait vider (c'est-à-dire écrire) des données pendant close. Je crois qu'il faut flushet fsyncsous la serrure pour s'assurer qu'aucune donnée supplémentaire n'est écrite en dehors de la serrure pendant close.
Benjamin Bannier
Merci pour la correction! J'ai vérifié qu'il est la possibilité pour une condition de course sans flushet fsync. J'ai ajouté les deux lignes que vous avez suggérées avant d'appeler unlock. J'ai retesté et la condition de course semble être résolue.
Thomas Lux
1
La seule chose qui va «mal» est que lorsque le processus 1 verrouille le fichier, son contenu sera tronqué (contenu effacé). Vous pouvez le tester vous-même en ajoutant un autre fichier «ouvert» avec un «w» au code ci-dessus avant le verrou. Ceci est cependant inévitable, car vous devez ouvrir le fichier avant de le verrouiller. Pour clarifier, le "atomique" est dans le sens où seul le contenu légitime d'un fichier sera trouvé dans un fichier. Cela signifie que vous n'obtiendrez jamais un fichier avec le contenu de plusieurs processus concurrents mélangés.
Thomas Lux
15

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

Ferrdo
la source
3
Cette bibliothèque semble bien écrite, mais il n'y a pas de mécanisme pour détecter les fichiers de verrouillage périmés. Il suit le PID qui a créé le verrou, il devrait donc être possible de savoir si ce processus est toujours en cours d'exécution.
sherbang
1
@sherbang: qu'en est-il de remove_existing_pidfile ?
Janus Troelsen
@JanusTroelsen le module pidlockfile n'acquiert pas les verrous de manière atomique.
sherbang
@sherbang Êtes-vous sûr? Il ouvre le fichier de verrouillage avec le mode O_CREAT | O_EXCL.
mhsmith
2
Veuillez noter que cette bibliothèque a été remplacée et fait partie de github.com/harlowja/fasteners
congusbongus
13

J'ai cherché plusieurs solutions pour ce faire et mon choix a été oslo.

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

Autres solutions:

  • Portalocker : nécessite pywin32, qui est une installation exe, donc pas possible via pip
  • fixations : mal documentées
  • lockfile : obsolète
  • flufl.lock : verrouillage de fichier sécurisé NFS pour les systèmes POSIX.
  • simpleflock : Dernière mise à jour 07/2013
  • zc.lockfile : Dernière mise à jour 2016-06 (à partir de 2017-03)
  • lock_file : Dernière mise à jour en 2007-10
Maxime Viargues
la source
re: Portalocker, vous pouvez maintenant installer pywin32 via pip via le package pypiwin32.
Timothy Jannace
13

Le verrouillage est spécifique à la plate-forme et à l'appareil, mais en général, vous avez quelques options:

  1. Utilisez flock () ou équivalent (si votre système d'exploitation le prend en charge). Il s'agit d'un verrouillage consultatif, sauf si vous vérifiez le verrou, il est ignoré.
  2. Utilisez une méthodologie de verrouillage-copie-déplacement-déverrouillage, dans laquelle vous copiez le fichier, écrivez les nouvelles données, puis déplacez-les (déplacer, pas copier - déplacer est une opération atomique sous Linux - vérifiez votre système d'exploitation), et vous vérifiez le existence du fichier de verrouillage.
  3. Utilisez un répertoire comme "verrou". Ceci est nécessaire si vous écrivez sur NFS, car NFS ne prend pas en charge flock ().
  4. Il y a aussi 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 de verrouillage rotatif (nouvelle tentative après échec) pour acquérir et tester le verrou. Cela laisse une petite fenêtre pour une mauvaise synchronisation, mais elle est généralement suffisamment petite pour ne pas être un problème majeur.

Si vous recherchez une solution multiplateforme, vous feriez mieux de vous connecter à un autre système via un autre mécanisme (la meilleure chose à faire est la technique NFS ci-dessus).

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

Richard Levasseur
la source
4
Remarque: Move / Rename n'est pas atomique dans Win32. Référence: stackoverflow.com/questions/167414/…
sherbang
4
Nouvelle note: os.renameest désormais atomique dans Win32 depuis Python 3.3: bugs.python.org/issue8828
Ghostkeeper
7

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

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

Kevin
la source
19
"processus séparé qui coordonne l'accès en lecture / écriture à ce fichier" - en d'autres termes, implémentez un serveur de base de données :-)
Eli Bendersky
1
C'est en fait la meilleure réponse. Dire simplement "utiliser un serveur de base de données" est trop simplifié, car une base de données ne sera pas toujours le bon outil pour le travail. Et s'il doit s'agir d'un fichier texte brut? Une bonne solution pourrait être de générer un processus enfant et d'y accéder via un tube nommé, un socket Unix ou une mémoire partagée.
Brendon Crawford
9
-1 parce que ce n'est que FUD sans explication. Verrouiller un fichier pour l'écriture me semble être un concept assez simple que les systèmes d'exploitation offrent avec des fonctions similaires flock. Une approche de "rouler vos propres mutex et un processus démon pour les gérer" semble être une approche plutôt extrême et compliquée à prendre pour résoudre ... un problème dont vous ne nous avez pas réellement parlé, mais que vous suggérez de façon effrayante existe.
Mark Amery
-1 pour les raisons données par @Mark Amery, ainsi que pour avoir émis une opinion non étayée sur les problèmes que l'OP veut résoudre
Michael Krebs
5

Le verrouillage d'un fichier est généralement une opération spécifique à la plate-forme, vous devrez peut-être prévoir la possibilité de s'exécuter 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"
Greg Hewgill
la source
7
Vous le savez peut-être déjà, mais le module de plateforme est également disponible pour obtenir des informations sur la plateforme en cours d'exécution. platform.system (). docs.python.org/library/platform.html .
monkut
2

J'ai travaillé sur une situation comme celle-ci où j'exécute plusieurs copies du même programme à partir du même répertoire / dossier et des erreurs de journalisation. 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 continuer, 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 à certains des commentaires sur les verrous périmés ci-dessus, j'ai modifié le code pour ajouter une vérification de l'exactitude du "fichier de verrouillage". Le chronométrage de plusieurs milliers d'itérations de cette fonction sur mon système a donné une moyenne de 0,002066 ... secondes juste avant:

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

à juste après:

remove('errloglock')

J'ai donc pensé que je commencerais avec 5 fois ce montant pour indiquer l'obsolescence et surveiller la situation pour les problèmes.

De plus, en travaillant avec le timing, j'ai réalisé 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.

barbe blanche
la source
2

Pour ajouter à la réponse d'Evan Fossmark , voici un exemple d'utilisation de filelock :

from filelock import FileLock

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

Tout code dans le with lock:bloc est thread-safe, ce qui signifie qu'il sera terminé avant qu'un autre processus ait accès au fichier.

Josh Correia
la source
1

Le scénario est comme ça: l'utilisateur demande un fichier pour faire quelque chose. Ensuite, si l'utilisateur envoie à nouveau la même requête, il informe l'utilisateur que la seconde requête n'est pas effectuée tant que la première requête n'est pas terminée. 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
Günay Gültekin
la source
0

J'ai trouvé une implémentation simple et efficace (!) De grizzled-python.

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

Speq
la source
4
L'option O_EXCL n'est pas liée au verrouillage
Sergei
0

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

Si vous souhaitez 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
Cobry
la source