“Chapter 4. Handle Errors and Exceptions in Programs” in “Introduction to Computer Programming with Python”
Chapter 4 Handle Errors and Exceptions in Programs
Errors in programs are inevitable but must be handled elegantly when they occur. In this chapter, you will learn how to raise and handle exceptions when errors occur in programs.
Learning Objectives
After completing this chapter, you should be able to
- • explain different types of errors and exceptions that may occur in a Python program.
- • write down the specific codes for some common types of exceptions.
- • use the try statement properly to handle possible exceptions raised by potential errors in a block of program code.
- • understand the cause of the following messages:
- ▪ TypeError
- ▪ NameError
- ▪ RuntimeError
- ▪ OSError
- ▪ ValueError
- ▪ ZeroDivisionError
- ▪ AssertionError
- ▪ FileNotFoundError
- • understand how to purposely throw an exception using a raise statement in a program.
- • use an assert statement to prevent future exceptions in a program.
- • list user-defined exceptions.
- • define a class of exceptions.
4.1 Errors in Your Programs
It is not unusual to have errors in your programs, especially for beginners. There are three types of errors in computer programs: syntax errors, runtime errors, and logic errors. If you program in a modern IDE such as VS Code, syntax errors can be easily avoided because whenever there is incorrect syntax, such as a misspelled keyword, you will be alerted by the IDE. Modern IDEs can even detect whether an identifier is used properly, such as when an undefined function is called or the value of a variable is used but no value has been previously assigned to the variable.
On the other hand, runtime errors only happen during the runtime of programs. Runtime errors are the most irritating to users and should be caught and handled gracefully in programs.
Logic errors are those caused by incorrect logic or operation in a program for a given problem or task. The following are some examples of logic errors:
- 1. An incorrect operator is used in an expression or statement, such as use + instead of −, > instead <, or vice versa.
- 2. The boundary of a sequence is miscounted.
Compared to syntax errors and runtime errors, the consequences of logic errors can be more costly because they often cause the program to produce incorrect results. It is even more concerning because logic errors often remain undetected until someone realizes that the result from the program is not what was expected. For example, syntax and runtime errors will produce no result, which will be immediately noticed by the user, whereas an incorrect result often goes unnoticed till it causes unexpected consequences, such as a missile being sent to the wrong target.
No programming techniques can help eliminate logic errors. It is up to the programmers to make the logic correct. That is why computer programmers should also take certain math courses.
Because Python programs are normally interpreted without compilation, syntax errors such as misspelled keywords are often found at runtime as well (unless the programs were developed in a smart IDE that can help identify the syntax errors while programming). However, the exception-handling mechanism provided by Python or other programming is not intended to catch syntax errors. You, as a programmer, must ensure that the programs you write use correct syntax, with the help of an IDE whenever is available.
You may be wondering what errors can be handled by the exception-handling mechanism provided by Python. The following is a long list of exception classes, all of which can be caused by errors in programs. The try-except statement provided by Python to handle exceptions and errors uses the names of exception classes to identify particular exceptions caused by specific errors. Remember at least the names of these commonly occurring exceptions or know where to find them.
Exception
This is the superclass of all exception classes to be detailed below. In the code sample below, any error will be caught and treated the same because it has “Exception” in the list of exceptions behind the except clause.
In [ ]: |
|
Out [ ]: | Give me an integer: 12 Give me another integer: 0 Wrong: It is not an integer or m is 0 |
In this particular case, m was given 0 so that the exception was caused by dividing n by 0, but the except clause could not tell because “Exception” is not a specific exception class.
When programming, you should put a code block in a try-except statement if you know that an error or errors might occur and you may even know what kind of error it may be. You want to have the errors handled elegantly. In the sample code above, because we ask for input from users and there is no guarantee that the user will not input a 0 for m, which will be used as the denominator or divisor, we put the code in a try-except to handle the possible exception raised by the error. Otherwise, the program would stop with a report of the error, as shown below:
In [ ]: |
|
Out [ ]: | Give me an integer: 12 Give me another integer: 0 ---------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-2-d0d8abede315> in <module> 1 n = int(input('give me an integer:')) 2 m = int(input('give me another integer:')) -> 3 n /= m # divide n by m ZeroDivisionError: division by zero |
The use of the try-except statement will be explained in the next section. For now, just remember the code lines between try and except are part of the program to be executed for the application, while the code under the except header tells what to do when a given exception has occurred.
ArithmeticError
The base class of all arithmetic errors, including OverflowError, ZeroDivisionError, and FloatingPointError, which means except ArithmeticError would be the same as except (OverflowError, ZeroDivisionError, FloatingPointError). Note that when there are multiple error names behind the except keyword, a pair of parentheses is used to enclose them.
OverflowError
This subclass of ArithmeticError is raised when the result of an arithmetic operation is too large to be represented.
A coding example of catching arithmetic errors like this is shown below:
In [ ]: |
|
Out [ ]: | Big x has 5091898 digits |
The code in the try clause calculates the power of 123567 to 999999 or 123567**999999. The code is put in a try-except statement because the result is expected to be very big and may overflow. Though the result has over five million digits, no exception is raised because the design and implementation of Python can handle very big numbers.
ZeroDivisionError
ZeroDivisionError is raised when the divisor of a division or module operation is 0. With this very specific exception/error name, the earlier example of this section can be rewritten as shown below:
In [ ]: |
|
Out [ ]: | Give me an integer: 23 Give me another integer: 0 Wrong: 0 cannot be used as a divisor! |
FloatingPointError
FloatingPointError is raised when a floating-point operation fails. However, Python does not raise such errors by default in its standard distribution. You will need a Python built with the -- with-fpectl flag, and import a module called fpectl when you want to turn the floating-point error control on or off.
AssertionError
AssertionError is raised when the assert statement fails. The assert statement is used to make an assertion on an assumed fact, such as whether a variable is defined, whether a variable is holding a specific value, or whether a value is a member of a sequence or set. If the assumed fact is not True, an AssertionError will be raised so that we know the assumed fact is untrue and we may need to deal with it, such as doing something else in the absence of the assumed fact.
In [ ]: |
|
Out [ ]: | ---------------------------------- AssertionError Traceback (most recent call last) <ipython-input-14-5a912881af6c> in <module> 1 vs = list(range(19)) # create a list with 19 members indexed from 0 to 18 ----> 2 assert(20 in vs) # 19 is out of the range > 18 AssertionError: |
AttributeError
AttributeError is raised when an attribute assignment or reference fails. Such an error will occur if you use an attribute of an object but the attribute itself does not exist.
In [ ]: |
|
Out [ ]: | ---------------------------------- AttributeError Traceback (most recent call last) <ipython-input-15-5dc5b1212e9f> in <module> 6 s0.lastname = 'Doe' 7 ----> 8 print(s0.fullname) AttributeError: 'Student' object has no attribute 'fullname' |
BufferError
BufferError is raised when a buffer-related operation cannot be performed. This often happens when working directly with computer memory and making restricted changes to a given memory area (buffer). The following is an example:
In [ ]: |
|
Out [ ]: | ---------------------------------- BufferError Traceback (most recent call last) <ipython-input-23-dc2c4a5f6bbd> in <module> 3 darray = io.BytesIO(data) # this creates a read-write copy of the bytearray. 4 dbuff = darray.getbuffer() # the memory of the bytearray is exported --> 5 darray.write(b'Hello World!') # raise error because the buffer is not changeable BufferError: Existing exports of data: object cannot be re-sized |
EOFError
EOFError is raised when the input() function hits the end-of-file condition.
GeneratorExit
GeneratorExit is raised when a generator’s close() method is called.
ImportError
ImportError is raised when the imported module is not found.
IndexError
IndexError is raised when the index of a sequence is out of range.
In [ ]: |
|
Out [ ]: | ---------------------------------- IndexError Traceback (most recent call last) <ipython-input-3-47e31ad8b75b> in <module> 1 vs = list(range(19)) ----> 2 vs[19] *= 3 IndexError: list index out of range |
KeyError
KeyError is raised when a key is not found in a dictionary.
In [ ]: |
|
Out [ ]: | ---------------------------------- KeyError Traceback (most recent call last) <ipython-input-4-3011ee6a346e> in <module> 1 vdict = {1:'One', 2:'Two', 3:'Three'} ----> 2 vdict[5] KeyError: 5 |
KeyboardInterrupt
KeyboardInterrupt is raised when the user hits the interrupt key (Ctrl+C or Delete).
MemoryError
MemoryError is raised when an operation runs out of memory.
ModuleNotFoundError
ModuleNotFoundError is raised by import when a module could not be located, or None is found in sys.modules.
In [ ]: |
|
Out [ ]: | ----------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-8-3808b892163e> in <module> ----> 1 import fpectl 2 round(14.5/0, 3) ModuleNotFoundError: No module named 'fpectl' |
NameError
NameError is raised when a variable is not found in the local or global scope.
In [ ]: |
|
Out [ ]: | ---------------------------------- NameError Traceback (most recent call last) <ipython-input-1-8b57ddde6300> in <module> ----> 1 print(what) NameError: name 'what' is not defined |
NotImplementedError
NotImplementedError is raised by abstract methods such as when an abstract method is called.
OSError
OSError is raised when a system operation causes a system-related error.
BlockingIOError
BlockingIOError is a subclass of OSError, raised when an operation would block on an object (e.g., a socket) set for a nonblocking operation.
ChildProcessError
ChildProcessError is a subclass of OSError, raised when an operation on a child process fails.
ConnectionError
ConnectionError is a subclass of OSError and a base class for connection-related issues.
BrokenPipeError
BrokenPipeError is a subclass of ConnectionError, raised when trying to write on a pipe while the other end has been closed or when trying to write on a socket that has been shut down for writing.
ConnectionAbortedError
ConnectionAbortedError is a subclass of ConnectionError, raised when a connection attempt is aborted by the peer.
ConnectionRefusedError
ConnectionRefusedError is a subclass of ConnectionError, raised when a connection attempt is refused by the peer.
ConnectionResetError
ConnectionResetError is a subclass of ConnectionError, raised when a connection is reset by the peer.
FileExistsError
FileExistsError is a subclass of OSError, raised when trying to create a file or directory that already exists.
FileNotFoundError
FileNotFoundError is a subclass of OSError, raised when a file or directory is requested but does not exist.
IsADirectoryError
IsADirectoryError is a subclass of OSError, raised when a file operation is requested on a directory.
NotADirectoryError
NotADirectoryError is a subclass of OSError, raised when a directory operation is requested on something that is not a directory.
PermissionError
PermissionError is a subclass of OSError, raised when trying to run an operation without adequate access rights such as filesystem permissions.
ProcessLookupError
ProcessLookupError is a subclass of OSError, raised when a given process doesn’t exist.
TimeoutError
TimeoutError is a subclass of OSError, raised when a system function has timed out at the system level.
RecursionError
RecursionError is a subclass of Exception, raised when the maximum recursion depth set by the system is exceeded. The set recursion depth can be found by calling sys.getrecursionlimit().
ReferenceError
ReferenceError is a subclass of Exception, raised when a weak reference proxy is used to access a garbage collection referent.
RuntimeError
RuntimeError is raised when an error does not fall under any other category.
StopIteration
StopIteration is raised by the next() function to indicate that there is no further item to be returned by the iterator.
StopAsyncIteration
StopAsyncIteration is raised by the __anext__() method of an asynchronous iterator object to stop the iteration.
SyntaxError
SyntaxError is raised by the parser when a syntax error is encountered.
In [ ]: |
|
Out [ ]: | File '<ipython-input-16-6e54ba8cdb35>', line 2 for i in range(10) ^ SyntaxError: invalid syntax |
IndentationError
IndentationError is raised when there is an incorrect indentation. Such errors may occur quite often at the beginning of your study of Python programming. You must pay great attention to it because indentation matters a lot in Python programs/scripts.
In [ ]: |
|
Out [ ]: | File '<tokenize>', line 4 i += 1 # not indented the same ^ IndentationError: unindent does not match any outer indentation level |
TabError
TabError is raised when the indentation consists of inconsistent tabs and spaces. Indentations can be made of spaces and tabs, but they need to be consistent to avoid such errors.
SystemError
SystemError is raised when the interpreter detects an internal error.
SystemExit
SystemExit is raised by the sys.exit() function.
TypeError
TypeError is raised when a function or operation is applied to an object of an incorrect type.
In [ ]: |
|
Out [ ]: | ---------------------------------- TypeError Traceback (most recent call last) <ipython-input-19-a90f29de94a2> in <module> ----> 1 sm = 10 + 'twenty' TypeError: unsupported operand type(s) for +: 'int' and 'str' |
UnboundLocalError
UnboundLocalError is raised when a reference is made to a local variable in a function or method but no value has been bound to that variable.
UnicodeError
UnicodeError is raised when a Unicode-related encoding or decoding error occurs.
UnicodeEncodeError
UnicodeEncodeError is raised when a Unicode-related error occurs during encoding.
UnicodeDecodeError
UnicodeDecodeError is raised when a Unicode-related error occurs during decoding.
UnicodeTranslateError
UnicodeTranslateError is raised when a Unicode-related error occurs during translation.
ValueError
ValueError is raised when a function gets the correct type of argument but an improper value.
In [ ]: |
|
Out [ ]: | ---------------------------------- ValueError Traceback (most recent call last) <ipython-input-20-6bbb9f319a0e> in <module> ----> 1 i = int('ten') ValueError: invalid literal for int() with base-10: 'ten' |
The following is a sample program to show how errors should be handled in a Python program:
# a python program to show how errors and exceptions are handled
# ask the user to enter two numbers
num1 = input("Enter the first integer number: ")
num2 = input("Enter the second integer number: ")
# try to convert the inputs to floats and divide them
try:
result = int(num1) / int(num2)
print(f"The result of dividing {num1} by {num2} is {result}.")
# handle the possible errors and exceptions
except ValueError:
print("Invalid input. Please enter numbers only.")
except ZeroDivisionError:
print("Cannot divide by zero. Please enter a nonzero number.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
The following exception will be raised when you run the code but input a literal instead of an integer:
Invalid input. Please enter numbers only.
If you type a 0 for the second input, the following exception will be raised:
Cannot divide by zero. Please enter a nonzero number.
Note that in real applications, you may not want a user to restart the program when an error has occurred. Instead, you may want to let the program continue until the user has given a valid input.
# a python program to show how errors and exceptions are handled
# initialize a flag to indicate if the division is successful
success = False
# use a while loop to keep asking for inputs until success is True
while not success:
# ask the user to enter two numbers
num1 = input("Enter the first number: ")
num2 = input("Enter the second number: ")
# try to convert the inputs to floats and divide them
try:
result = int(num1) / int(num2)
print(f"The result of dividing {num1} by {num2} is {result}.")
# set success to True if no error occurs
success = True
# handle the possible errors and exceptions
Zexcept ValueError:
print("Invalid input. Please enter numbers only.")
except ZeroDivisionError:
print("Cannot divide by zero. Please enter a nonzero number.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
This will ensure the program will continue until two integer numbers are received and the division is successful.
Now let us solve a real problem: write a Python program to find all perfect numbers in a given range set by a user. A perfect number is an integer number that is equal to the sum of all factors, including 1 but excluding itself. For example, 6 is a perfect number because 6 = 1 + 2 + 3, and 1, 2, and 3 are all its factors.
So basically, the steps to take, or the algorithm, will be as follows:
- 1. Get two integers from user to set the range, assigned to a and b, respectively. If a is greater than b, then we swap their values.
- 2. Loop through all the numbers between a and b, including a and b, and test each number to see if it is a perfect number; if yes, we add it to a list holding perfect numbers found so far. The list should be set as empty at the beginning.
- 3. Print out all the perfect numbers in the list, and stop.
To test a number to see if it is perfect, according to the definition above, we need to first find out its factors and check whether the sum of all the factors is equal to the number itself. So there will be two additional steps:
- 2.1. Find out all the factors, and keep them all in a list.
- 2.2. Check whether the number is equal to the sum of all its factors in the list built up in step 2.1.
The following is one implementation of the algorithm in Python, taken from a Jupyter Notebook cell:
# a python program to find all the perfect numbers in a range
# set by the user
# first get two integers from user
# initialize a flag to indicate if the division is successful
success = False
# use a while loop to keep asking for inputs until success is True
while not success:
# ask the user to enter two numbers
num1 = input("Enter the first number: ")
num2 = input("Enter the second number: ")
# try to convert the inputs to floats and divide them
try:
a, b = int(num1), int(num2)
# set success to True if no error occurs
success = True
if a>b: # then we need to swap a and b
c = a
a = b
b = c
perfect_list = [] # make an empty list ready to hold all perfect numbers
for n in range(a, b+1): # we said b is included
# start finding all factors of n
factor_list = [1] # make a list with 1 as a single element
for f in range(2,n): # start from 2, with n as excluded from factors
if n%f == 0: # f is a factor of n
if n%f == 0: # f is a factor of n
if not f in factor_list:
factor_list.append(f)
# now we have a list of factors for n
if n == sum(factor_list): # n is a perfect number
perfect_list.append([n, factor_list]) # keep factors too for checking
# now we have found all the perfect numbers in the range
print(f"Perfect numbers found between {a} and {b}:")
for n in perfect_list:
print(n, end=" ")
# handle the possible errors and exceptions
except ValueError:
print("Invalid input. Please enter numbers only.")
except ZeroDivisionError:
print("Cannot divide by zero. Please enter a nonzero number.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
When running the program with Jupyter Notebook in VS Code, by pressing Ctrl+Enter and inputting 3 and 1000 for the two numbers requested, the following output will be produced:
- Perfect numbers found between 3 and 1000:
- [6, [1, 2, 3]] [28, [1, 2, 4, 7, 14]] [496, [1, 2, 4, 8, 16, 31, 62, 124, 248]]
The output shows both the perfect numbers and a list of their factors.
4.2 Handling Runtime Errors and Exceptions
The runtime errors and exceptions discussed can be fatal or nonfatal. By a nonfatal error, we mean that when the error occurs, the program can still continue if it is properly handled. In contrast, when a fatal error occurs, the program stops and has to be fixed before you can restart the program.
When programming, since runtime errors (or “exceptions,” to use a more techie term) are unavoidable, the best a programmer can do is to have all exceptions caught and handled properly when they occur. Otherwise, what renders to the users when an error has occurred could be overwhelming and irritating. Consider the following code example, which simply asks for a numeric grade.
In [ ]: |
|
When a letter grade is entered instead, a runtime error occurred, and Python interpreter or Python Virtual Machine (PVM) renders a bunch of information about the error, as shown below:
Out [ ]: | Please input your grade: A ---------------------------------- ValueError Traceback (most recent call last) <ipython-input-13-d0126eb94acb> in <module> ----> 1 grade = int(input('Please input your grade: ')) ValueError: invalid literal for int() with base-10: 'A' |
Although the information above may be helpful for programmers to debug the code and correct the error, it is completely useless for ordinary users. For that reason, all modern programming languages provide some way to handle runtime errors and exceptions so that only sensible information can be seen by users and, in most cases, programs can continue and recover from errors peacefully, without crashing.
Similar to those in other modern programming languages, Python exceptions are handled with a try statement. The general coding structure of try statements is as follows:
# the try clause is for enclosing a code block of the program
# in which errors may occur
try:
<code block in the normal code flow> # things to do normally
except <exception/error 1>: # the except clause is for handling errors
<code block for exception 1> # things to do if exception 1 happens
except <exception/error 2>: # except clause may have a list of errors
<code block for exception 2> # things to do if exception 2 happens
except <exception/error 3>: # there can be more exceptions
<code block for exception 3> # things to do if exception 3 happens
else: # do something when no error has occurred
<code block for else> # things to do when no exceptions are raised
finally: # finally clause allows to do something regardless of the above
<code block for finally> # things to do regardless of the above
As shown, a try statement starts with a try clause, which encloses the code that you want to run for the application but may cause errors and raise exceptions.
Right after the try clause is one or more exception clauses, each of which starts with the keyword “except,” followed by a system-defined error/exception name. The code of each exception clause specifies what you want to do if this error happens.
In [ ]: |
|
Out [ ]: | Please input your grade: A Exception has been raised! invalid literal for int() with base-10: 'A's |
Note that in the code above, ValueError is the name of a built-in exception class. We put this code in a try statement because you do not know whether the user will type an integer as expected. If a nonnumber is typed, an error will occur for function int.
The else clause encloses the code specifying what to do if no errors are caught by all the exception blocks.
The finally clause encloses the code that will run regardless of whether any exception has been raised within the try clause.
Now comes the question of when you need to put a code block in a try statement. You cannot put every piece of code in a try statement. The rule is that if a statement or block of code may cause an error during runtime, especially when the possibility is out of your control, the statement or code should be enclosed in a try statement and each of the possible errors should be handled properly. Examples of such a possibility may include the following situations:
- 1. Statements involve user input, because you never know if a user will do what you expected.
- 2. Statements involve the use of files, because there is no guarantee as to the status of the file. It may have been deleted when you want to read it; it may already have existed when you want to create a new one with same name.
- 3. Statements involve the use of resources on the web or on the internet in general because the resources may no longer be available.
- 4. Statements involve the use of numbers from user input or involve calculation, where 0 may end up as a denominator.
- 5. Statements involve extensive use of computer memory, which may lead to the depletion of RAM.
Chapter Summary
- • Programs can have syntax errors and logic errors.
- • Errors that are found when running the program are runtime errors.
- • A good IDE can often help you avoid many syntax errors, including undefined names, incorrect indentation, incorrect formation of statements, and more. If there are syntax errors in your program, look for marks that your IDE may have added to indicate them in your code.
- • Logic errors are caused by incorrect problem solving or programming logic. Examples include values that are out of the intended range and the incorrect use of operators or functions.
- • Some logic errors can raise exceptions at runtime. For example, if an index variable for a sequence (string, list, or tuple) is out of range, an exception will be raised.
- • Some logic errors can only be discovered by programmers, system testers, or users who notice an unexpected result or behaviour in the system.
- • A Python interpreter or virtual machine will raise exceptions when a runtime error has occurred. If exceptions are not caught and properly handled, the application or system will crash and become very ugly.
- • Python and many other modern programming languages use try-except statements to handle exceptions.
- • In Python, a try statement may take one of five forms: try-except, try-except-else, try-except-finally, try-except-else-finally, and try-finally.
- • Between try and except, there should be a code block that may have runtime errors (such as incorrect input from users, files, or network sockets). The try keyword and the code block together form a try clause; right behind the except keyword, there should be the name or names of errors, followed by a code block to be run when the named error or errors occurred, which together form an except clause.
- • A try clause can be followed by multiple except clauses.
- • An except clause can be followed by an else clause, which consists of the else keyword and a code block to be run when an error has occurred but didn’t match any of the named errors in the except clauses.
- • The finally clause can be used as a final clause of a try statement. It consists of the finally keyword and a code block that is to be run in all circumstances, whether an error has occurred or not.
Exercises
- 1. Suppose that you want to get an integer from a user, but you are concerned that the user may type something else other than an integer. Write a piece of code, as concise as possible, that asks the user for input until an integer is entered by the user.
- 2. What error will occur when running the following code?
- a.
s = 8 + 'a'
- b.
students = ['John', 'May', 'Jim']
- c.
total = sum(12, 90, 32, 'one hundred')
- a.
- 3. What’s wrong with the following code?
idx = 1
product = 0
while idx>10:
product *= idx
idx++
print(product)
We use cookies to analyze our traffic. Please decide if you are willing to accept cookies from our website. You can change this setting anytime in Privacy Settings.