I wonder how with
statements work. I am trying to convert the following:
with obj() as o:
do_something()
into this:
o = obj.__enter__()
try:
do_something()
except Exception as e:
obj.__exit__(type(e),e, **I don't know what should be here**)
else:
obj.__exit__(None, None , None)
So how it will be? If I am wrong in anywhere, please correct me. I want to know what to replace the
**I don't know what should be here**
with.
According to PEP-343, which introduced context managers, the code
with obj() as o:
do_something()
is equivalent to
mgr = obj()
exit = type(mgr).__exit__
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
o = value
do_something()
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
Some notes:
o = type(mgr).__enter__(mgr)
because the name o
is only defined if __enter__
does not raise an exception, allowing us to enter the try
statement at all. (There are other ways to handle this, but this is how I interpret PEP-343's translation.)__exit__
can be called in two different places. If we catch an exception, we pass information about that to __exit__
, which can prevent the calling code from seeing it if it returns True
.finally
block ensures that __exit__
is called exactly once. Namely, we want to call it if no exceptions were raised, but not again if the first call swallowed an exception by returning True
or raised an exception itself.