Exception Handling
We’ve already seen exceptions in various places.
Python gives NameError
when we try to use a variable that is not defined.
print(no_such_variable)
We get a ValueError
when we convert convert an invalid value to integer.
n = int("bad-number")
We get a FileNotFoundError
error when try to open a file that doesn't exist.
contents = open("no-file").read()
In all these above cases, Python is raising an exception and the default behavior on exceptions is to crash. However, it is possible to except these exceptions and recover from them.
Python uses try-except
statements to handle exceptions. It is typically used this way:
try:
somecode_that_can_raise_exceptions()
except SomeException:
recover_from_exception()
For example, the following code has a toint
function that returns 0
when a bad value is given as input.
def toint(strvalue):
try:
return int(strvalue)
except ValueError:
print("Bad number:", strvalue)
return 0
a = "5"
b = "bad-number"
c = toint(a) + toint(b)
print(c)
We can also use optional else
and finally
clauses with try-except
.
The else
clause is used to specify code whe no exception happens and finally
is used to specify code that need to run whether or not exception happens. The finally
is typically used to close the resources created.
value = 'a'
# value = '10'
try:
int(value)
except ValueError:
print("Bad value", value)
else:
print("in else")
finally:
print("in finally")
Understanding Tracebacks
When an exception happens, Python prints the traceback execution. Let's look at an example to understand it better.
def read_file(filename):
return open(filename).read()
def read_words(filename):
contents = read_file(filename)
return contents.split()
def main():
filename = "bad-file"
words = read_words(filename)
print(f"found {len(words)} words")
main()
You can see that the traceback has the following:
Traceback (most recent call last):
File "/app/main.py", line 13, in <module>
main()
File "/app/main.py", line 10, in main
words = read_words(filename)
File "/app/main.py", line 5, in read_words
contents = read_file(filename)
File "/app/main.py", line 2, in read_file
return open(filename).read()
FileNotFoundError: [Errno 2] No such file or directory: 'bad-file'
You can see the flow of execution that caused the error, including the actual line of code that have contributed to the error. Here is quick summary of the traceback:
main
function was called in line 13- that called
read_words
in line 10 - that called
read_file
in line 5 - that called
open
in line 2 and that failed
Whenever you see a traceback, usually the last entry is the most interesting one. But sometimes, the last few entries would be in standard library modules and we need to move up to see which code written by us is contributing to the error.
Raising Exceptions
We can raise an exception using the raise
statement.
def withdraw(amount):
if amount < 0:
raise ValueError("Can't withdraw a negative amount")
withdraw(-10)
We can also create our exceptions. The following example creates a new exception BankException
and raised it when
class BankException(Exception):
pass
balance = 100
def withdraw(amount):
global balance
if amount > balance:
raise BankException("Insufficient balance")
balance = balance - amount
print(f"{amount} withdrawn. Current balance is {balance}.")
withdraw(50)
withdraw(80)