I wrote this example to show myself that __exit__
is not being run when an exception occurs:
class A(object):
def __enter__(self):
print('enter')
def __exit__(self):
print('exit')
try:
with A() as a:
raise RunTimeError()
except Exception as e:
print('except')
Output:
enter
except
That said, what is the correct way to use a with
statement and catch exceptions, while making sure __exit__
is being run in the end?
Thanks!
The __exit__
function is called, regardless whether the with
body raises errors or not. Your function needs to have additional parameters exc_type
(the type of the exception), exc_value
(the exception object), and traceback
(the traceback that is generated).
In case the with
body did not raise an Error
, the three parameters are None
. In case there is an error, they take the values described above.
But you can for instance close a file, regardless whether there is an error and then later handle the error.
So we can here implement it for instance as:
class A(object):
def __enter__(self):
self.file = open('some_file.txt')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
print(('exit', exc_type, exc_value, traceback))
# close the file, regardless of exceptions
self.file.close()
return False # silence the exception?
If we now write something like:
with A():
raise Exception
We will obtain the exception, the __exit__
function will print:
('exit', <class 'Exception'>, Exception(), <traceback object at 0x7fc512c924c8>)
We can inspect the exception class, exception value and traceback, and handle accordingly. For instance based on the exception, we might decide to close a file, send a bug report, abort the SQL transaction or not.
The __exit__
function also has a return value (if not specified, a Python function returns None
). In case the __exit__
function returns an object with truthiness True
, it will surpress the exception: the exception will not raise out of the with
block. Otherwise it will be raised out of the with
block. For instance in our example, we still receive the exception.
class SilenceExceptions(object):
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
return True # silence ALL exceptions
If we now call:
with SilenceExceptions():
raise Error
we will not see the exception, since it is "catched" in the __exit__
function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With