Exceptions
The language C does not provide any mechanism for handling errors. That is a pity because in C, we must always handle error propagation by writing functions that returns 0 in general, and a value different from 0 in case of an error. The code becomes horrible.
We also have nasty setjmp
and longjmp
which are dangerous!. Many languages, like C++, Python, Javascript, Java, C# etc. offer exceptions to handle errors. They work like setjmp
and longjmp
, but they take care about the memory.
3/0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[1], line 1
----> 1 3/0
ZeroDivisionError: division by zero
import logging
y = 0
try:
x = 3/y
except ZeroDivisionError:
logging.error("You are crazy to try a division by zero!")
ERROR:root:You are crazy to try a division by zero!
This is particularly useful when the error occurs deep in the program.
def division(x, y):
return x/y
def f(x, y):
return division(x, y) + 1
try:
print(f(3, 0))
except ZeroDivisionError:
print("You are crazy to try a division by zero!")
You are crazy to try a division by zero!
Syntax of try-except-else-finally
try:
print("write here the code what may cause an exception")
except ZeroDivisionError:
print("write here what to do in case of a division by zero")
except AssertionError:
print("write here what to do in case of an assert condition was false")
else:
print("write here some code to execute when no error occurred")
finally:
print("write here code which is always executed (error or not)")
write here the code what may cause an exception
write here some code to execute when no error occurred
write here code which is always executed (error or not)
def f():
try:
x = 3/0
except ZeroDivisionError:
print("write here what to do in case of a division by zero")
return
except AssertionError:
print("write here what to do in case of an assert condition was false")
else:
print("write here some code to execute when no error occurred")
finally:
print("write here code which is always executed (error or not)")
f()
write here what to do in case of a division by zero
Hierarchy of exceptions
from graphviz import Digraph
dot = Digraph(graph_attr=dict(rankdir="BT"), edge_attr={'arrowhead':'empty'})
dot.edges([("Exception", "BaseException"),
("KeyboardInterrupt", "BaseException"),
("SystemExit", "BaseException"),
("ZeroDivisionError", "Exception"),
("AssertionError", "Exception"),
("ValueError", "Exception"),
])
dot
try:
3/0
except BaseException:
print("All base exceptions are handled")
NEVER catch a BaseException. The program should STOP in that case
All exceptions are BaseException
. Normally, you do not catch KeyboardInterrupt
, SystemExit
because you prevent your program from stopping if the user wants so!
The following code is bad:
try:
print("my program")
except BaseException:
print("NEVER catch a BaseException. The program should STOP in that case")
try:
print("my program")
except KeyboardInterrupt:
print("NEVER catch a BaseException. The program should STOP in that case")
However, you can catch exceptions that inherits from Exception
User-defined exceptions
You can define your own type of exceptions.
class DenominatorZeroError(Exception):
pass
def f(x,y):
if y == 0:
raise DenominatorZeroError
return x/y
try:
d = f(3, 0)
except DenominatorZeroError:
print("Denominator should be non-zero!")
Denominator should be non-zero!
class DenominatorZeroError(Exception):
pass
def g(x):
return DenominatorZeroError if x == 0 else ValueError
def f(x,y):
if y == 0:
raise g(0)
return x/y
try:
d = f(3, 0)
except DenominatorZeroError:
print("Denominator should be non-zero!")
Denominator should be non-zero!
class OddNumberOfVertexError(Exception):
def __init__(self, nbVertices):
super().__init__(f"should give a graph with an even number of vertices: {nbVertices} given")
def computePerfectMatching(G):
if G["nbVertices"] % 2 != 0:
raise OddNumberOfVertexError(G["nbVertices"])
try:
computePerfectMatching({"nbVertices": 3})
except OddNumberOfVertexError as e:
print(e)
should give a graph with an even number of vertices: 3 given
Ressource management
Bad practice
In Python, it is highly not-recommended to write:
for line in open('example.txt'): # bad
...
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[14], line 1
----> 1 for line in open('example.txt'): # bad
2 ...
File /usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py:310, in _modified_open(file, *args, **kwargs)
303 if file in {0, 1, 2}:
304 raise ValueError(
305 f"IPython won't let you open fd={file} by default "
306 "as it is likely to crash IPython. If you know what you are doing, "
307 "you can use builtins' open."
308 )
--> 310 return io_open(file, *args, **kwargs)
FileNotFoundError: [Errno 2] No such file or directory: 'example.txt'
The issue is that... we do not close the file! Also do not write (even many websites say it):
f = open("example.txt")
print(file.read())
file.close()
In case of an error in the treatment, the file may not be closed.
Solution
Instead we should a construction which has the following form:
file = EXPR
file.__enter__()
try:
BLOCK
finally:
file.__exit__()
This is cumbersome. So thanks to https://peps.python.org/pep-0343/ we have a with-as
construction:
with EXPR as file:
BLOCK
with open('example.txt') as file:
content = file.read()
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 with open('example.txt') as file:
2 content = file.read()
File /usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py:310, in _modified_open(file, *args, **kwargs)
303 if file in {0, 1, 2}:
304 raise ValueError(
305 f"IPython won't let you open fd={file} by default "
306 "as it is likely to crash IPython. If you know what you are doing, "
307 "you can use builtins' open."
308 )
--> 310 return io_open(file, *args, **kwargs)
FileNotFoundError: [Errno 2] No such file or directory: 'example.txt'
We can also use several ressources as follows.
with open('input.txt') as fInput, open("output.txt", "w") as fOutput:
for line in fInput:
fOutput.write(line)