Bloquear un archivo en Python

Jay Shaw 15 febrero 2024
  1. Impacto del uso compartido de recursos por múltiples procesos en Python
  2. Estado del archivo y su impacto en el bloqueo de un archivo en Python
  3. Bloquear un archivo en Python
  4. Conclusión
Bloquear un archivo en Python

Algunos recursos no pueden ser accedidos por dos personas simultáneamente en nuestra vida diaria, por ejemplo, un vestuario. Cada vestuario tiene una cerradura a la que solo puede acceder una persona a la vez.

Si dos personas tuvieran acceso simultáneamente, podría causar confusión y vergüenza. Entonces, es por eso que el recurso compartido está protegido con un candado.

De manera similar, en la programación, cada vez que dos procesos o subprocesos comparten el mismo recurso, puede crear problemas que deben evitarse mediante el uso de bloqueos. Este artículo explicará cómo bloquear un archivo en Python.

Impacto del uso compartido de recursos por múltiples procesos en Python

Esta sección aclarará por qué es importante bloquear un archivo en Python.

El programa tiene dos procesos: el primero deposita $1 en una cuenta, otro deduce $1 de la misma y luego se imprime el saldo al final. Esta operación se ejecuta dentro de un bucle miles de veces.

Técnicamente, la iteración no produciría ningún cambio en el saldo final, ya que se suma y resta el mismo número una y otra vez. Pero cuando se compila el programa, los resultados son variables.

import multiprocessing


# Withdrawal function


def wthdrw(bal):
    for _ in range(10000):
        bal.value = bal.value - 1


# Deposit function


def dpst(bal):
    for _ in range(10000):
        bal.value = bal.value + 1


def transact():
    # initial balance
    bal = multiprocessing.Value("i", 100)

    # creating processes
    proc1 = multiprocessing.Process(target=wthdrw, args=(bal,))
    proc2 = multiprocessing.Process(target=dpst, args=(bal,))

    # starting processes
    proc1.start()
    proc2.start()

    # waiting for processes to finish
    proc1.join()
    proc2.join()

    # printing final balance
    print("Final balance = {}".format(bal.value))


if __name__ == "__main__":

    for _ in range(10):
        # Performing transaction process
        transact()

Producción :

C:\python38\python.exe "C:\Users\Win 10\main.py"
Final balance = 428
Final balance = 617
Final balance = -1327
Final balance = 1585
Final balance = -130
Final balance = -338
Final balance = -73
Final balance = -569
Final balance = 256
Final balance = -426

Process finished with exit code 0

Analicemos el código de ejemplo para comprender por qué sucede.

Este programa utiliza el paquete de la biblioteca de Python multiprocesamiento para invocar métodos como procesos en el programa.

Los dos métodos, wthdrw y dpst, deducen y añaden dinero a la cuenta. Tienen un parámetro, bal, que significa equilibrio.

Dentro del método, el valor de bal se incrementa o decrementa dentro de un bucle for. La iteración se repite 10000 veces.

El bal.value es el recurso compartido en este programa.

# Method to Decrement
def wthdrw(bal):
    for _ in range(10000):
        bal.value = bal.value - 1

Un nuevo método, transact(), lanza los procesos uno sobre otro. Dentro del método, se crea el objeto bal, que almacena el valor inicial, y el saldo se establece en 100.

def transact():
    bal = multiprocessing.Value("i", 100)
    lock = multiprocessing.Lock()

La librería multiprocesamiento permite lanzar métodos como procesos utilizando objetos. Por ejemplo, el método wthdrw se lanza creando un proceso proc1.

Este proceso apunta al método wthdrw y pasa bal y un objeto vacío como argumentos.

proc1 = multiprocessing.Process(target=wthdrw, args=(bal,))

Del mismo modo, se crea un proceso proc2 para lanzar el método dpst. Una vez creados ambos procesos, se inician utilizando process_name.start().

proc1.start()
proc2.start()

Para cerrar un proceso en ejecución, se utiliza la sintaxis process_name.join(). Pone en cola la solicitud detrás de un proceso en ejecución, por lo que el sistema espera a que el proceso finalice y luego lo cierra.

El saldo final se imprime una vez que se completa el proceso.

print("Final balance = {}".format(bal.value))

La variable __name__ se establece en __main, y el método transact() se llama dentro de un bucle for. Esto nos permite observar las iteraciones varias veces sin llamar manualmente a los procesos.

if __name__ == "__main__":

    for _ in range(10):
        transact()

Cuando se ejecutó el programa, encontramos que los valores eran inconsistentes. El recurso compartido bal.value no está protegido con un candado, por lo que el otro proceso edita el valor antes de que finalice el proceso en ejecución.

Los bloqueos se utilizan para resolver este problema. El programa anterior se bloquea colocando un candado en ambos métodos; de esta manera, el compilador tiene que esperar para ejecutar el siguiente proceso antes de que se complete el anterior.

# Withdrawal function
def wthdrw(bal, lock):
    for _ in range(10000):
        # Locks
        lock.acquire()
        bal.value = bal.value - 1
        lock.release()

La instrucción lock.acquire() bloquea el proceso y, dentro de ella, se edita el valor de bal. Después de eso, el bloqueo se libera usando lock.release().

Lo mismo se aplica al método dpst, y el resto del programa permanece igual.

Producción :

C:\python38\python.exe "C:\Users\Win 10\main.py"
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100
Final balance = 100

Process finished with exit code 0

Como sabemos lo que sucede con varios procesos con recursos compartidos, veremos una funcionalidad importante necesaria para bloquear un archivo en Python: el estado del archivo.

Estado del archivo y su impacto en el bloqueo de un archivo en Python

Un estado de archivo muestra si el archivo está abierto o cerrado. Es importante conocer el estado del archivo para bloquear un archivo en Python porque las operaciones de bloqueo solo se pueden realizar en archivos abiertos.

En Python, suponga que hay un archivo - myFile.txt. Cuando escribimos:

readMyFile = open("myFile.txt", "r").read()

Es imposible obtener el estado del archivo, cerrarlo o hacer cualquier otra cosa. No hay acceso porque no se almacena en ningún lado.

Dado que no tendría ningún valor descartar la información y la imposibilidad de la recolección de basura, no existe una magia que permita recuperar el conocimiento que se ha perdido.

Absténgase de almacenar cualquier valor destinado a ser utilizado en una variable (una lista, un miembro de dictado o un atributo de una instancia), lo que permitirá su uso.

myFile = open("myFile.txt", "r")
readMyFile = myFile.read()
print(myFile.closed)

myFile.close()
print(myFile.closed)

El código anterior guarda el archivo en una variable myFile. Luego, otra variable, readMyFile, usa la variable myFile para leer el objeto.

El uso de este método mantiene el archivo en un estado abierto incluso después de que se completa la lectura.

La sintaxis print(myFile.closed) muestra el estado actual del archivo; aquí, devuelve un valor false, lo que significa que no está cerrado. Este archivo se cierra explícitamente usando myFile.close().

Como el cierre explícito de archivos es propenso a causar errores humanos, una mejor solución es utilizar la instrucción with.

with open("myFile.txt", "r") as myFile:
    readMyFile = myFile.read()

Ahora, myFile se cierra automáticamente sin necesidad de cerrarlo explícitamente. Del ejemplo anterior, entendimos que los métodos eficientes para abrir y cerrar archivos son esenciales para bloquear un archivo en Python.

Un archivo solo se puede bloquear en un estado abierto y un archivo no se puede desbloquear cuando no hay un bloqueo presente. El bloqueo de un archivo en Python siempre debe ejecutarse dentro de un bloque with.

Veamos varias formas de bloquear un archivo en python.

Bloquear un archivo en Python

Los bloqueos de archivos son generalmente bibliotecas de terceros que se usan dentro de scripts para bloquear un archivo. En esta sección, se analizan algunos bloqueos de archivos ampliamente utilizados.

Bloquear un archivo usando FileLock en Python

Esta es una biblioteca de bloqueo de archivos específica de la plataforma que funciona tanto en Windows como en UNIX. Para instalarlo, escriba el comando:

pip install filelock

Bloquear un archivo con este método es muy simple. Abra el archivo con FileLock dentro de una instrucción with, para que el archivo se abra y el sistema lo bloquee.

Una vez que se completa el proceso, el bloqueo se libera automáticamente al final de la declaración “con”.

from filelock import FileLock

with FileLock("myfile.txt"):
    print("Lock acquired.")

Producción :

C:\python38\python.exe "C:/main.py"
Lock acquired.

Process finished with exit code 0

Otra forma de bloquear un archivo en Python es con tiempos de espera. Esto ayuda a poner un límite de tiempo si un archivo no se puede bloquear, liberando así identificadores de archivos para otros procesos.

from filelock import FileLock

file = "example.txt"
lockfile = "example.txt.lock"

lock = FileLock(lockfile, timeout=5)

lock.acquire()
with open(file, "a") as f:
    print("File Locked")
    f.write("Add some data \n")
lock.release()


with open(file, "a") as f:
    f.write("Second time writing \n")

La primera línea de código importa el módulo FileLock de la biblioteca filelock. Un archivo example.txt se guarda dentro de un archivo variable; este archivo se utilizará para escribir en él.

El bloqueo no se coloca directamente en el archivo original, por lo que se crea un archivo temporal, como example.txt.lock. La variable lock se utiliza para crear un objeto de bloqueo para el lockfile, y se establece un tiempo de espera de 5 segundos.

Este bloqueo se puede colocar usando la instrucción lock.acquire. Un bloque with se usa para abrir el archivo y escribir en él, mientras que lockfile evita cualquier otro proceso para acceder al archivo original mientras se está escribiendo.

Finalmente, el bloqueo se libera usando lock.release(). Luego, otro proceso abre el archivo y escribe con éxito en él.

Producción :

C:\python38\python.exe "C:/main.py"
File Locked

Process finished with exit code 0

Archivo de bloqueo de Python usando FileLock - Salida

Para bloquear un archivo usando Python, también podemos colocar los bloqueos de forma anidada:

from filelock import Timeout, FileLock

lock = FileLock("high_ground.txt.lock")
with lock:
    with open("high_ground.txt", "a") as f:
        f.write("You were the chosen one.")

En el ejemplo anterior, se crea un objeto de bloqueo con un archivo llamado high_ground.txt para bloquear un archivo en Python. La cerradura se coloca dentro de un bloque; dentro de él, el archivo se lee usando otro bloque.

Este método requiere menos código que el anterior.

El FileLock depende de la plataforma. Si todas las instancias de la aplicación operan en la misma plataforma, use un FileLock; de lo contrario, utilice un SoftFileLock.

Un SoftFileLock es independiente de las plataformas y solo monitorea la existencia del archivo de bloqueo. Debido a esto, es extremadamente portátil y es más probable que se bloquee si la aplicación falla.

En tales circunstancias, elimine el archivo de bloqueo.

El casillero de archivos también se puede usar con un bloque de manejo de excepciones:

try:
    with lock.acquire(timeout=10):
        with open(file_path, "a") as f:
            f.write("I have a bad feeling about this.")
except Timeout:
    print("Another instance of this application currently holds the lock.")

Este fragmento de código coloca un candado dentro del bloque try y otorga un tiempo de espera de 10 segundos. Luego, dentro de un con anidado, se escribe el archivo.

Dentro del bloque excepto, se configura un mensaje adecuado para que se imprima si se agota el tiempo de espera de la aplicación.

Bloquear un archivo usando PortaLocker en Python

Otra opción para bloquear un archivo en Python es usar la biblioteca llamada portalocker, que ofrece una API simple para el bloqueo de archivos.

Es crucial recordar que los bloqueos son una advertencia por defecto en los sistemas Linux y Unix. El bloqueo obligatorio de archivos se puede habilitar en Linux agregando la opción -o mand al comando de montaje.

Para instalarlo, escriba el comando:

pip install "portalocker[redis]"

Bloquear un archivo en Python usando portalocker es similar al método FileLock pero mucho más simple.

import portalocker

file = "example.txt"
with portalocker.Lock(file, "a") as fh:
    fh.write("first instance")
    print("waiting for your input")
    input()

Producción :

C:\python38\python.exe "C:/main.py"

lock acquired
waiting for your input

En el código anterior, la primera línea importa el paquete de la biblioteca. El candado se crea con la sintaxis portalocker.RedisLock(), y el candado se coloca con una instrucción with.

Para colocar el bloqueo dentro de un tiempo de espera, use:

import portalocker

file = "example.txt"
with portalocker.Lock("file", timeout=1) as fh:
    print("writing some stuff to my cache...", file=fh)

Producción:

Archivo de bloqueo de Python usando PortaLocker - Salida

Conclusión

Este artículo explica cómo bloquear un archivo en Python y por qué es importante. El lector puede crear archivos de estado abierto y bloquearlos fácilmente después de leer este artículo.

Artículo relacionado - Python File