I am learning about context managers and was trying to build one myself. The following is a dummy context manager that opens a file in read mode (I know I can just do with open(...): ...
. this is just an example I built to help me understand how to make my own context managers):
@contextmanager
def open_read(path: str):
f = open(path, 'r')
print('open')
yield f
f.close()
print('closed')
def foo():
try:
with open_read('main.py') as f:
print(f.readline())
raise Exception('oopsie')
except Exception:
pass
print(f.readline())
foo()
I expect this code to print:
open
<line 1 of a.txt>
closed
ValueError: I/O operation on closed file.
But instead it prints:
open
<line 1 of a.txt>
<line 2 of a.txt>
It didn't close the file!
This seems to contradict python's docs which state that __exit__
will be called whether the with
statement exited successfully or with an exception:
object.exit(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.
Interestingly, when I reimplemented the context manager as shown below, it worked as expected:
class open_read(ContextDecorator):
def __init__(self, path: str):
self.path = path
self.f = None
def __enter__(self):
self.f = open(self.path, 'r')
print('open')
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
print('closed')
Why didn't my original implementation work?
try-finally
block, so when the exception is re-raised inside the generator it just propagates out. Seecontextmanager
in the docs – Cathar