Buscar..


Introducción

Si bien los administradores de contexto de Python son ampliamente utilizados, pocos entienden el propósito detrás de su uso. Estas declaraciones, comúnmente utilizadas con los archivos de lectura y escritura, ayudan a la aplicación a conservar la memoria del sistema y mejorar la administración de recursos al garantizar que los recursos específicos solo se usan para ciertos procesos. Este tema explica y demuestra el uso de los gestores de contexto de Python.

Sintaxis

  • con "context_manager" (como "alias") (, "context_manager" (como "alias")?) *:

Observaciones

Los gestores de contexto se definen en PEP 343 . Están diseñados para ser utilizados como un mecanismo más sucinto para la administración de recursos que el que try ... finally construye. La definición formal es la siguiente.

En este PEP, los gestores de contexto proporcionan los __enter__() y __exit__() que se invocan al ingresar y salir del cuerpo de la declaración with.

Luego pasa a definir la instrucción with lo siguiente.

with EXPR as VAR:
    BLOCK

La traducción de la declaración anterior es:

   mgr = (EXPR)
   exit = type(mgr).__exit__  # Not calling it yet
   value = type(mgr).__enter__(mgr)
   exc = True
   try:
       try:
           VAR = value  # Only if "as VAR" is present
           BLOCK
       except:
           # The exceptional case is handled here
           exc = False
           if not exit(mgr, *sys.exc_info()):
               raise
           # The exception is swallowed if exit() returns true
   finally:
       # The normal and non-local-goto cases are handled here
       if exc:
           exit(mgr, None, None, None)

Introducción a los gestores de contexto y con la declaración.

Un administrador de contexto es un objeto que se notifica cuando un contexto (un bloque de código) comienza y termina . Normalmente se utiliza uno con la instrucción with . Se encarga de la notificación.

Por ejemplo, los objetos de archivo son gestores de contexto. Cuando un contexto finaliza, el objeto de archivo se cierra automáticamente:

open_file = open(filename)
with open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

El ejemplo anterior generalmente se simplifica utilizando la palabra clave as :

with open(filename) as open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

Cualquier cosa que finalice la ejecución del bloque hace que se llame al método de salida del administrador de contexto. Esto incluye excepciones y puede ser útil cuando un error hace que salga prematuramente de un archivo abierto o conexión. Salir de un script sin cerrar correctamente los archivos / conexiones es una mala idea, ya que puede causar la pérdida de datos u otros problemas. Al utilizar un administrador de contexto, puede asegurarse de que siempre se tomen precauciones para evitar daños o pérdidas de esta manera. Esta característica fue añadida en Python 2.5.

Asignar a un objetivo

Muchos administradores de contexto devuelven un objeto cuando se ingresa. Puede asignar ese objeto a un nuevo nombre en la declaración with .

Por ejemplo, usar una conexión de base de datos en una declaración with podría darle un objeto de cursor:

with database_connection as cursor:
    cursor.execute(sql_query)

Los objetos de archivo se devuelven solos, esto hace posible abrir el objeto de archivo y usarlo como administrador de contexto en una expresión:

with open(filename) as open_file:
    file_contents = open_file.read()

Escribiendo tu propio gestor de contexto.

Un administrador de contexto es cualquier objeto que implementa dos métodos mágicos __enter__() y __exit__() (aunque también puede implementar otros métodos):

class AContextManager():

    def __enter__(self):
        print("Entered")
        # optionally return an object
        return "A-instance"

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exited" + (" (with an exception)" if exc_type else ""))
        # return True if you want to suppress the exception

Si el contexto sale con una excepción, la información sobre esa excepción se pasa como un triple exc_type , exc_value , traceback (estas son las mismas variables que devuelve el sys.exc_info() función). Si el contexto sale normalmente, los tres de estos argumentos serán None .

Si ocurre una excepción y se pasa al método __exit__ , el método puede devolver True para suprimir la excepción, o la excepción volverá a presentarse al final de la función __exit__ .

with AContextManager() as a:
    print("a is %r" % a)
# Entered
# a is 'A-instance'
# Exited

with AContextManager() as a:
    print("a is %d" % a)
# Entered
# Exited (with an exception)
# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# TypeError: %d format: a number is required, not str

Tenga en cuenta que en el segundo ejemplo, a pesar de que se produce una excepción en medio del cuerpo de la instrucción with, el controlador __exit__ aún se ejecuta, antes de que la excepción se propague al ámbito externo.

Si solo necesita un método __exit__ , puede devolver la instancia del administrador de contexto:

class MyContextManager:
    def __enter__(self):
        return self

    def __exit__(self):
        print('something')

Escribiendo tu propio administrador de contexto usando la sintaxis del generador.

También es posible escribir un administrador de contexto usando la sintaxis del generador gracias al decorador contextlib.contextmanager :

import contextlib

@contextlib.contextmanager
def context_manager(num):
    print('Enter')
    yield num + 1
    print('Exit')

with context_manager(2) as cm:
    # the following instructions are run when the 'yield' point of the context
    # manager is reached.
    # 'cm' will have the value that was yielded
    print('Right in the middle with cm = {}'.format(cm))

produce:

Enter
Right in the middle with cm = 3
Exit

El decorador simplifica la tarea de escribir un administrador de contexto al convertir un generador en uno. Todo antes de que la expresión de rendimiento se convierta en el método __enter__ , el valor generado se convierta en el valor devuelto por el generador (que se puede vincular a una variable en la declaración with), y todo después de la expresión de rendimiento se convierte en el método __exit__

Si el administrador de contexto debe manejar una excepción, se puede escribir un try..except..finally try..except..finally en el generador, y este bloque de excepción manejará cualquier excepción que se genere en el bloque with .

@contextlib.contextmanager
def error_handling_context_manager(num):
    print("Enter")
    try:
        yield num + 1
    except ZeroDivisionError:
        print("Caught error")
    finally:
        print("Cleaning up")
    print("Exit")

with error_handling_context_manager(-1) as cm:
    print("Dividing by cm = {}".format(cm))
    print(2 / cm)

Esto produce:

Enter
Dividing by cm = 0
Caught error
Cleaning up
Exit

Gestores de contexto multiples

Puede abrir varios gestores de contenido al mismo tiempo:

with open(input_path) as input_file, open(output_path, 'w') as output_file:

    # do something with both files. 
    
    # e.g. copy the contents of input_file into output_file
    for line in input_file:
        output_file.write(line + '\n')

Tiene el mismo efecto que los administradores de contexto de anidamiento:

with open(input_path) as input_file:
    with open(output_path, 'w') as output_file:
        for line in input_file:
            output_file.write(line + '\n')

Gestionar recursos

class File():
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.open_file = open(self.filename, self.mode)
        return self.open_file

    def __exit__(self, *args):
        self.open_file.close()

__init__() configura el objeto, en este caso, configura el nombre del archivo y el modo para abrir el archivo. __enter__() abre y devuelve el archivo y __exit__() simplemente lo cierra.

El uso de estos métodos mágicos ( __enter__ , __exit__ ) le permite implementar objetos que se pueden usar fácilmente with la instrucción with.

Usar clase de archivo:

for _ in range(10000):
    with File('foo.txt', 'w') as f:
        f.write('foo')


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow