from IPython.core.display import HTML
HTML(open("custom.html", "r").read())
In Python error conditions are handled using so called "exceptions". This is an example for an exception:
x = 1 / 0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[2], line 1 ----> 1 x = 1 / 0 ZeroDivisionError: division by zero
The default mode is that such an exception stops program execution.
As a Python programmer we can change this behavior by catching an exception
try:
x = 1 / 0
except ZeroDivisionError:
print("only chuck norris can divide by zero!")
only chuck norris can divide by zero!
This will only catch the zero division error. Any other error which could occur between try
and except
will not be caught:
try:
x = y / 0
except ZeroDivisionError:
print("only chuck norris can divide by zero!")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[4], line 2 1 try: ----> 2 x = y / 0 3 except ZeroDivisionError: 4 print("only chuck norris can divide by zero!") NameError: name 'y' is not defined
You can chain exception handling, in case you want to handle different exceptions in a different way:
try:
# y is not defined here and we divide by zero:
x = y / 0
except ZeroDivisionError:
print("only chuck norris can divide by zero!")
except NameError:
print("what ??")
what ??
you can also use the same handler for multiple exceptions:
try:
x = y / 0
except (ZeroDivisionError, NameError):
print("oops")
oops
If an exception is triggerend within a nested function call we see the call stack:
def divide(a, b):
return a / b
def inverse(a):
return divide(1, a)
print(inverse(0))
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[7], line 9 5 def inverse(a): 6 return divide(1, a) ----> 9 print(inverse(0)) Cell In[7], line 6, in inverse(a) 5 def inverse(a): ----> 6 return divide(1, a) Cell In[7], line 2, in divide(a, b) 1 def divide(a, b): ----> 2 return a / b ZeroDivisionError: division by zero
You see in the output above that the previous script called inverse(0)
which then called divide(1, 0)
which finally caused the ZeroDivisionError
.
The exception "bubbles" the up the call stack until the top level, and as it is not caught the stack-trace is printed.
We can intercept this at any point of the call stack:
def divide(a, b):
return a / b
def inverse(a):
try:
return divide(1, a)
except ZeroDivisionError:
return None
print(inverse(0))
None
To check if a given string represents a number, we can either come up with a solution based an analysing the given string character by character, or we use exception handling:
def is_float(string):
try:
float(string)
except ValueError:
return False
return True
print(is_float("1.2"))
print(is_float("1.ab"))
True False
Exceptions can also be risen on demand to indicate error conditions:
def fun(number):
if number < 0.0:
raise ValueError(f"{number=} is negative! ")
return number
print(fun(1.0))
1.0
# read the output below line by line !!!
print(fun(-1.0))
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[12], line 2 1 # read the output below line by line !!! ----> 2 print(fun(-1.0)) Cell In[10], line 3, in fun(number) 1 def fun(number): 2 if number < 0.0: ----> 3 raise ValueError(f"{number=} is negative! ") 4 return number ValueError: number=-1.0 is negative!
To do processing and re-raise a possibly unknown error, save it first using the as
keyword:
try:
divide_by_zero(1)
except ZeroDivisionError:
print("Infinity!")
except Exception as e:
print("Opps, fallback! Smth went wrong: ", e)
raise e
Opps, fallback! Smth went wrong: name 'divide_by_zero' is not defined
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[13], line 7 5 except Exception as e: 6 print("Opps, fallback! Smth went wrong: ", e) ----> 7 raise e Cell In[13], line 2 1 try: ----> 2 divide_by_zero(1) 3 except ZeroDivisionError: 4 print("Infinity!") NameError: name 'divide_by_zero' is not defined
These are all exceptions available per default in Python, you can also see that there is a hierarchy:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
So in the diagram, you can see that the depicted hierarchy ZeroDivisionError
is below ArithmeticError
. This means we also could handle 1 / 0
using:
try:
1 / 0
except ArithmeticError:
print("oops")
oops
The drawback here is that this also catches FloatingPointError
and OverflowError
.
WARNING: Don't catch the Exception
or even BaseException
without re-raising them. This will catch also any programming mistake you make and will make debugging very difficult.
You can also create your own exceptions by sub-classing from a given exception (details about sub-classing are in the other script about object oriented programming):
class MyNumericalError(ArithmeticError):
pass
raise MyNumericalError("don't like numbers")
--------------------------------------------------------------------------- MyNumericalError Traceback (most recent call last) Cell In[15], line 6 1 class MyNumericalError(ArithmeticError): 3 pass ----> 6 raise MyNumericalError("don't like numbers") MyNumericalError: don't like numbers
finally
keyword¶In addition to except
one can also declare actions to be executed in error cases as well in situation where code works as it should. This is done using finally
:
def div(a, b):
try:
return a / b
except ZeroDivisionError:
return None
finally:
print("div done")
div(1, 2)
div(1, 0)
div done div done
A simple way to check some conditions and to raise an exeption if this condition is not met, is the assert
statement:
def square_root(x):
assert x >= 0, "does not work with negative values"
return x**0.5
print(square_root(4))
print(square_root(-1))
2.0
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Cell In[17], line 7 3 return x**0.5 6 print(square_root(4)) ----> 7 print(square_root(-1)) Cell In[17], line 2, in square_root(x) 1 def square_root(x): ----> 2 assert x >= 0, "does not work with negative values" 3 return x**0.5 AssertionError: does not work with negative values
"int"
, "float"
or "str"
according to the the three cases.N1 op N2
where N1
and N2
are numbers and op
is one of +
, -
, /
, *
and **
. You can assume spaces around op
. The function splits the string and evaluates the final result. Catch exceptions for all numerical computations involved. Transform all such exceptions as well as format errors in the input (no spaces, no numbers, invalid operation, ...) to a InvalidTerm
exception which you have to implement on your own.