Exception Handling in Python

The usual way of handling error cases is using exceptions. A function raises an exception if some requirement condition is not met or something unexpected happens.

Here are some examples of exceptions. You've may have seen many of these already.

If you try to access a variable that is not defined, Python raises a NameError.

In [21]:
no_such_variable
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-b7c1357f8e68> in <module>()
----> 1 no_such_variable

NameError: name 'no_such_variable' is not defined

Trying to convert invalid number as int raises a ValueError.

In [22]:
int("bad-number")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-22-30a0b3005a3a> in <module>()
----> 1 int("bad-number")

ValueError: invalid literal for int() with base 10: 'bad-number'

Trying to open a non existing file in read mode raises a FileNotFoundError.

In [23]:
open("no-file.txt")
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-23-0d982c33ba3e> in <module>()
----> 1 open("no-file.txt")

FileNotFoundError: [Errno 2] No such file or directory: 'no-file.txt'

When an exception is raised, it Python exits the program after printing the traceback.

In [25]:
%%file sq2.py

def square(x):
    return x*x

def sum_of_squares(x, y):
    return square(x) + square(y)

def main():
    result = sum_of_squares(3, "4")
    print(result)

if __name__ == "__main__":
    main()
Overwriting sq2.py
In [26]:
!python sq2.py
Traceback (most recent call last):
  File "sq2.py", line 13, in <module>
    main()
  File "sq2.py", line 9, in main
    result = sum_of_squares(3, "4")
  File "sq2.py", line 6, in sum_of_squares
    return square(x) + square(y)
  File "sq2.py", line 3, in square
    return x*x
TypeError: can't multiply sequence by non-int of type 'str'

Pay attention to the the traceback above. It prints the stack of the functions active when the exception occurred.

Q: What happens if there are two statements that are causing exceptions. Will both of them be executed?

In [29]:
def f():
    print("BEGIN f")
    x = 1 + "2"
    print("middle line")
    y = "1" + 2
    print("END f")
    return x+y
In [30]:
f()
BEGIN f
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-30-0ec059b9bfe1> in <module>()
----> 1 f()

<ipython-input-29-70412ac6d542> in f()
      1 def f():
      2     print("BEGIN f")
----> 3     x = 1 + "2"
      4     print("middle line")
      5     y = "1" + 2

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Exceptions can be handled by using the try-except statements.

For example, the following function reads a file if available otherwise returns empty string.

In [31]:
import sys

def readfile(filename):
    """Returns the contents of the given file.
    
    If the file is not found, empty string is returned.
    """
    try:
        return open(filename).read()
    except FileNotFoundError:
        print("WARNING: file {} is not found".format(filename), file=sys.stderr)
        return ""
In [32]:
readfile("nofile.txt")
WARNING: file nofile.txt is not found
Out[32]:
''
In [33]:
readfile("three.txt")
Out[33]:
'one\ntwo\nthree'

The except statement takes the class of the exception to be handle. It is even possible to have multiple except blocks for handling multiple errors.

In [38]:
def readint(filename):
    try:
        text = open(filename).read()
        return int(text)
    except FileNotFoundError as e:
        print("ERROR: File not Found: " + filename, file=sys.stderr)
    except ValueError as e:
        print("ERROR: Invalid integer: ", repr(text), file=sys.stderr)
    return 0
In [36]:
%%file 5.txt
5
Overwriting 5.txt
In [37]:
readint("5.txt")
Out[37]:
5
In [39]:
readint("nofile.txt")
ERROR: File not Found: nofile.txt
Out[39]:
0
In [40]:
readint("three.txt")
ERROR: Invalid integer:  'one\ntwo\nthree'
Out[40]:
0
In [ ]:
 
In [ ]:
 

Raising exceptions

Exceptions can be raised using raise statement.

In [41]:
class ValidationError(Exception):
    pass

def register(username, password):
    validate_username(username)
    validate_password(password)
    # do_registration()
    
def validate_username(username):
    if len(username) < 3:
        raise ValidationError("The username must have at least 3 characters")
        
def validate_password(password):
    if len(password) < 8:
        raise ValidationError("The password must have at least 8 characters")
    
In [42]:
register("anand", "pw")
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-42-587505347707> in <module>()
----> 1 register("anand", "pw")

<ipython-input-41-8ba0bb842210> in register(username, password)
      4 def register(username, password):
      5     validate_username(username)
----> 6     validate_password(password)
      7     # do_registration()
      8 

<ipython-input-41-8ba0bb842210> in validate_password(password)
     13 def validate_password(password):
     14     if len(password) < 8:
---> 15         raise ValidationError("The password must have at least 8 characters")
     16 

ValidationError: The password must have at least 8 characters
In [ ]:
 
In [ ]:
 
In [ ]:
 

Problem: Write a function safeint to convert given string to an integer. The function should accept two arguments, the string to be converted and a default value. If the given string is not a valid integer, the default value should be returned.

>>> safeint('3', 0)
3
>>> safeint('N/A', 0)
0
In [ ]:
# your code here
In [ ]:
safeint('3', 0)
In [ ]:
safeint('N/A', 0)
In [ ]:
 
In [ ]:
 
In [ ]:
 

Problem: Write a program sumfile.py to compute sum of all the numbers in a file. It is expected that there'll be one number per line. If there are any invalid numbers in the file, they should be ignored after printing a warning.

$ python sumfile.py num.txt
WARNING: Bad Number 'N/A'
WARNING: Bad Number 'xx'
15
In [52]:
%%file sumfile.py
import sys

def safeint(strvalue, default):
    try:
        return int(strvalue)
    except ValueError:
        print("WARNING: Invalid number", repr(strvalue))
        return default

def sumfile(filename):
    numbers = [safeint(line, 0) for line in open(filename)]
    return sum(numbers)

def main():
    filename = sys.argv[1]
    result = sumfile(filename)
    print(result)

if __name__ == "__main__":
    main()
Overwriting sumfile.py
In [53]:
%%file num.txt
1
2
3
N/A
4
5
xx
Overwriting num.txt
In [48]:
%%file good.txt
1
2
3
4
5
Overwriting good.txt
In [49]:
!python sumfile.py good.txt
15
In [54]:
!python sumfile.py num.txt
WARNING: Invalid number 'N/A\n'
WARNING: Invalid number 'xx'
15
In [ ]:
 
In [ ]: