Quelle est la façon la plus élégante de résoudre ce problème:
Les fonctions intégrées fonctionnent comme ceci
>>> 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 contient maintenant '111'.
>>> file2.close()
scr.txt a été remplacé et contient désormais '222' (sous Windows, Python 2.4).
La solution doit fonctionner à l'intérieur du même processus (comme dans l'exemple ci-dessus) ainsi que lorsqu'un autre processus a ouvert le fichier.
Il est préférable, si un programme en panne ne garde pas le verrou ouvert.
Je ne pense pas qu'il existe une méthode entièrement multiplateforme. Sous Unix, le module fcntl le fera pour vous. Cependant sur les fenêtres (que je suppose que vous êtes par les chemins), vous devrez utiliser le module win32file.
Heureusement, il existe une implémentation portable ( portalocker ) utilisant la méthode appropriée de la plate-forme dans le livre de recettes python.
Pour l'utiliser, ouvrez le fichier, puis appelez:
portalocker.lock(file, flags)
où les indicateurs sont portalocker.LOCK_EX pour un accès en écriture exclusif ou LOCK_SH pour un accès en lecture partagé.
La solution doit fonctionner à l'intérieur du même processus (comme dans l'exemple ci-dessus) ainsi que lorsqu'un autre processus a ouvert le fichier.
Si par "un autre processus" vous voulez dire "quel que soit le processus" (c'est-à-dire pas votre programme), sous Linux, il n'y a aucun moyen d'y parvenir en se basant uniquement sur les appels système ( fcntl & copains). Ce que vous voulez c'est verrouillage obligatoire , et la manière Linux de l'obtenir est un peu plus compliquée:
Remontez la partition qui contient votre fichier avec l'option mand :
# mount -o remount,mand /dev/hdXY
Définissez l'indicateur sgid pour votre fichier:
# chmod g-x,g+s yourfile
Dans votre code Python, obtenez un verrou exclusif sur ce fichier:
fcntl.flock(fd, fcntl.LOCK_EX)
Désormais, même cat ne pourra pas lire le fichier tant que vous n'aurez pas relâché le verrou.
EDIT: Je l'ai résolu moi-même! En utilisant existence du répertoire et l'âge comme mécanisme de verrouillage! Le verrouillage par fichier n'est sûr que sous Windows (car Linux écrase silencieusement), mais le verrouillage par répertoire fonctionne parfaitement à la fois sous Linux et Windows. Voir mon GIT où j'ai créé une classe facile à utiliser 'lockbydir.DLock' pour cela:
https://github.com/drandreaskrueger/lockbydir
Au bas du fichier lisez-moi, vous trouverez 3 GITplayers où vous pouvez voir les exemples de code s'exécuter en direct dans votre navigateur! Assez cool, non? :-)
Merci de votre attention
Je voudrais répondre à parity3 ( https://meta.stackoverflow.com/users/1454536/parity ) mais je ne peux ni commenter directement ('Vous devez avoir 50 points de réputation pour commenter'), ni puis-je voir un moyen de le contacter directement? Que me suggérez-vous pour le joindre?
Ma question:
J'ai implémenté quelque chose de similaire à ce que parity3 a suggéré ici comme réponse: https://stackoverflow.com/a/21444311/3693375 ("En supposant que votre interprète Python, et le ...")
Et cela fonctionne à merveille - sous Windows. (Je l'utilise pour implémenter un mécanisme de verrouillage qui fonctionne sur des processus démarrés indépendamment. https://github.com/drandreaskrueger/lockbyfile )
Mais à part la parité3, cela ne fonctionne PAS de la même manière sous Linux:
os.rename (src, dst)
Renommez le fichier ou le répertoire src en dst. ... Sous Unix, si dst existe et est un fichier, il sera remplacé silencieusement si l'utilisateur a l'autorisation. L'opération peut échouer sur certaines versions Unix si src et dst sont sur des systèmes de fichiers différents. En cas de succès, le changement de nom sera une opération atomique (il s'agit d'une exigence POSIX). Sous Windows, si dst existe déjà, OSError sera déclenché ( https://docs.python.org/2/library/os.html#os.rename )
Le remplacement silencieux est le problème. Sous Linux. Le "si dst existe déjà, OSError sera levé" est idéal pour mes besoins. Mais uniquement sous Windows, malheureusement.
Je suppose que l'exemple de parity3 fonctionne toujours la plupart du temps, en raison de sa condition if
if not os.path.exists(lock_filename):
try:
os.rename(tmp_filename,lock_filename)
Mais alors le tout n'est plus atomique.
Parce que la condition if peut être vraie dans deux processus parallèles, puis les deux seront renommés, mais un seul gagnera la course de changement de nom. Et aucune exception levée (sous Linux).
Aucune suggestion? Merci!
P.S .: Je sais que ce n'est pas la bonne façon, mais il me manque une alternative. VEUILLEZ ne pas me punir en diminuant ma réputation. J'ai beaucoup regardé autour de moi pour résoudre ce problème moi-même. Comment PM utilisateurs ici? Et meh pourquoi je ne peux pas?
Voici un début sur la moitié win32 d'une implémentation portable, qui n'a pas besoin d'un mécanisme de verrouillage séparé.
Nécessite Python pour les extensions Windows pour passer à l'api win32, mais c'est à peu près obligatoire pour python sur Windows déjà, et peut également être fait avec ctypes . Le code pourrait être adapté pour exposer plus de fonctionnalités si nécessaire (comme autoriser FILE_SHARE_READ
plutôt que pas de partage du tout). Voir aussi la documentation MSDN pour les appels système CreateFile
et WriteFile
et article sur la création et l'ouverture de fichiers .
Comme cela a été mentionné, vous pouvez utiliser le module standard fcntl pour implémenter la moitié unix de cela, si nécessaire.
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)
Voici comment se comporte votre exemple ci-dessus:
>>> 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
En supposant que votre interpréteur Python, et le système d'exploitation et le système de fichiers sous-jacents traitent os.rename comme une opération atomique et qu'il produira une erreur lorsque la destination existe, la méthode suivante est exempte de conditions de concurrence. J'utilise ceci en production sur une machine linux. Ne nécessite aucune bibliothèque tierce et ne dépend pas du système d'exploitation, et à part une création de fichier supplémentaire, le résultat de performance est acceptable pour de nombreux cas d'utilisation. Vous pouvez facilement appliquer le modèle de décorateur de fonction de python ou une 'with_statement' contextmanager ici pour résumer le gâchis.
Vous devrez vous assurer que lock_filename n'existe pas avant le début d'un nouveau processus/tâche.
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'
[~ # ~] modifier [~ # ~]
Il est apparu que os.rename remplace silencieusement la destination sur un système d'exploitation non Windows. Merci de l'avoir signalé @ akrueger!
Voici une solution de contournement, provenant de ici :
Au lieu d'utiliser os.rename, vous pouvez utiliser:
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 Vous êtes probablement très bien avec votre solution basée sur les répertoires, vous donnant simplement une autre méthode.
Je préfère utiliser filelock , une bibliothèque multiplateforme Python bibliothèque qui nécessite à peine tout code supplémentaire. Voici un exemple de la façon de l'utiliser:
from filelock import FileLock
lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
file = open(path, "w")
file.write("111")
file.close()
Tout code dans le with lock:
block est thread-safe, ce qui signifie qu'il sera terminé avant qu'un autre processus n'ait accès au fichier.
Pour vous protéger lors de l'ouverture de fichiers dans une seule application, vous pouvez essayer quelque chose comme ceci:
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)
De cette façon, vous sous-classe la classe file
. Maintenant, faites simplement:
>>> 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
Si vous l'ouvrez d'abord avec le mode 'w', il ne permettra plus d'ouvrir, même en mode lecture, comme vous le vouliez ...