Tengo el siguiente código:
class Test: def __init__(self, name): self.name = name def __enter__(self): print(f'entering {self.name}') def __exit__(self, exctype, excinst, exctb) -> bool: print(f'exiting {self.name}') return True with Test('first') as test: print(f'in {test.name}') test = Test('second') with test: print(f'in {test.name}')
Ejecutarlo produce el siguiente resultado:
entering first exiting first entering second in second exiting second
Pero esperaba que produjera:
entering first in first exiting first entering second in second exiting second
¿Por qué no se llama el código dentro de mi primer ejemplo?
El método __enter__
debería devolver el objeto de contexto. with ... as ...
usa el valor de retorno de __enter__
para determinar qué objeto darle. Dado que su __enter__
no devuelve nada, implícitamente devuelve None
, por lo que test
es None
.
with Test('first') as test: print(f'in {test.name}') test = Test('second') with test: print(f'in {test.name}')
Así que test
es ninguna. Entonces test.name
es un error. Ese error se genera, por lo que se llama a Test('first').__exit__
. __exit__
devuelve True
, lo que indica que el error se manejó (esencialmente, que su __exit__
está actuando como un bloque de except
), por lo que el código continúa después del primer bloque with
, ya que le dijo a Python que todo estaba bien.
Considerar
def __enter__(self): print(f'entering {self.name}') return self
También puede considerar no devolver True
de __exit__
a menos que realmente tenga la intención de suprimir incondicionalmente todos los errores en el bloque (y comprenda completamente las consecuencias de suprimir los errores de otros programadores, así como KeyboardInterrupt
, StopIteration
y varias señales del sistema)
El problema es que su método __enter__
devuelve None
. Por lo tanto, a test
se le asigna None
.
Luego intenta acceder a (None).name
, lo que genera un error. Dado que su método __exit__
siempre devuelve True
, suprimirá cualquier error. Según los documentos :
Devolver un valor verdadero de este método hará que la declaración with suprima la excepción y continúe la ejecución con la declaración que sigue inmediatamente a la declaración with.
Creo que este comportamiento se debe a que __enter__
debe devolver algo sobre lo que se operará, que en este caso se accederá con el nombre test
. Cambiando __enter__
a lo siguiente
def __enter__(self): print(f"entering {self.name}") return self
obtenemos el comportamiento esperado.