with manager context python python-3.x contextmanager

with - context manager python



Llamando__enter__ y__exit__ manualmente (2)

No, no hay nada de malo en eso. Incluso hay lugares en la biblioteca estándar que lo hacen. Al igual que el módulo de multiprocessing :

class SemLock(object): def __init__(self, kind, value, maxvalue, *, ctx): ... try: sl = self._semlock = _multiprocessing.SemLock( kind, value, maxvalue, self._make_name(), unlink_now) except FileExistsError: pass ... def __enter__(self): return self._semlock.__enter__() def __exit__(self, *args): return self._semlock.__exit__(*args)

O el módulo tempfile :

class _TemporaryFileWrapper: def __init__(self, file, name, delete=True): self.file = file self.name = name self.delete = delete self._closer = _TemporaryFileCloser(file, name, delete) ... # The underlying __enter__ method returns the wrong object # (self.file) so override it to return the wrapper def __enter__(self): self.file.__enter__() return self # Need to trap __exit__ as well to ensure the file gets # deleted when used in a with statement def __exit__(self, exc, value, tb): result = self.file.__exit__(exc, value, tb) self.close() return result

Los ejemplos de la biblioteca estándar no llaman a __enter__ / __exit__ para dos objetos, pero si tienes un objeto que es responsable de crear / destruir el contexto para múltiples objetos en lugar de solo uno, llamar a __enter__ / __exit__ para todos ellos está bien.

El único gotcha potencial es manejar adecuadamente los valores de retorno de las llamadas __enter__ __exit__ para los objetos que está administrando. Con __enter__ , debe asegurarse de que está devolviendo el state requerido para que el usuario de su objeto de envoltura pueda regresar de la llamada with ... as <state>: . Con __exit__ , debe decidir si desea propagar cualquier excepción que haya ocurrido dentro del contexto (devolviendo False ) o suprimirla (devolviendo True ). Sus objetos gestionados podrían intentar hacerlo de cualquier manera, debe decidir qué tiene sentido para el objeto envoltorio.

He buscado en Google calling __enter__ manually pero sin suerte. Así que imaginemos que tengo una clase de conector MySQL que usa las funciones __enter__ y __exit__ (originalmente usadas with sentencias) para conectar / desconectar de una base de datos.

Y tengamos una clase que use 2 de estas conexiones (por ejemplo, para la sincronización de datos). Nota: este no es mi escenario de la vida real, pero parece ser el ejemplo más simple .

La forma más fácil de hacer que todo funcione en conjunto es la siguiente clase:

class DataSync(object): def __init__(self): self.master_connection = MySQLConnection(param_set_1) self.slave_connection = MySQLConnection(param_set_2) def __enter__(self): self.master_connection.__enter__() self.slave_connection.__enter__() return self def __exit__(self, exc_type, exc, traceback): self.master_connection.__exit__(exc_type, exc, traceback) self.slave_connection.__exit__(exc_type, exc, traceback) # Some real operation functions # Simple usage example with DataSync() as sync: records = sync.master_connection.fetch_records() sync.slave_connection.push_records(records)

P : ¿Está bien (hay __enter__ error) llamar __enter__ / __exit__ manualmente de esta manera?

Pylint 1.1.0 no emitió ninguna advertencia sobre esto, ni he encontrado ningún artículo al respecto (enlace de Google al principio).

¿Y qué hay de llamar?

try: # Db query except MySQL.ServerDisconnectedException: self.master_connection.__exit__(None, None, None) self.master_connection.__enter__() # Retry

¿Es esta una buena / mala práctica? ¿Por qué?


Tu primer ejemplo no es una buena idea:

  1. ¿Qué sucede si slave_connection.__enter__ lanza una excepción:

    • master_connection adquiere su recurso
    • slave_connection falla
    • DataSync.__enter__ la excepción
    • DataSync.__exit__ no se ejecuta
    • master_connection nunca se limpia!
    • Potencial para cosas malas
  2. ¿Qué sucede si master_connection.__exit__ lanza una excepción?

    • DataSync.__exit__ terminó temprano
    • slave_connection nunca se limpia!
    • Potencial para cosas malas

contextlib.ExitStack puede ayudar aquí:

def __enter__(self): with ExitStack() as stack: stack.enter_context(self.master_connection) stack.enter_context(self.slave_connection) self._stack = stack.pop_all() return self def __exit__(self, exc_type, exc, traceback): self._stack.__exit__(self, exc_type, exc, traceback)

Haciendo las mismas preguntas:

  1. ¿Qué sucede si slave_connection.__enter__ lanza una excepción:

    • Se master_connection bloque with y se limpia la stack master_connection
    • ¡Todo está bien!
  2. ¿Qué sucede si master_connection.__exit__ lanza una excepción?

    • No importa, slave_connection se limpia antes de que se llame
    • ¡Todo está bien!
  3. Ok, ¿qué pasa si slave_connection.__exit__ lanza una excepción?

    • ExitStack se asegura de llamar a master_connection.__exit__ lo que suceda con la conexión de esclavo
    • ¡Todo está bien!

No hay nada de malo en llamar a __enter__ directamente, pero si necesita llamar a más de un objeto, ¡asegúrese de limpiar correctamente!