Errors and Exceptions in Python
Errors and Exceptions in Python
Errors
Errors or mistakes in a program are often referred to as bugs. They
are almost always the fault of the programmer. The process of
finding and eliminating errors is called debugging. Errors can be
classified into three major groups:
Syntax errors
Runtime errors
Logical errors
Syntax errors
Python will find these kinds of errors when it tries to parse your
program, and exit with an error message without running anything.
Syntax errors are mistakes in the use of the Python language, and
are analogous to spelling or grammar mistakes in a language like
English: for example, the sentence Would you some tea? does not
make sense – it is missing a verb.
Note
1
Python will do its best to tell you where the error is located, but
sometimes its messages can be misleading: for example, if you
forget to escape a quotation mark inside a string you may get a
syntax error referring to a place later in your code, even though
that is not the real source of the problem. If you can’t see anything
wrong on the line specified in the error message, try backtracking
through the previous few lines. As you program more, you will get
better at identifying and fixing errors.
else:
print("Hello!")
if mark >= 50
print("You passed!")
if arriving:
print("Hi!")
esle:
print("Bye!")
if flag:
print("Flag is set!")
Runtime errors
2
Some examples of Python runtime errors:
division by zero
performing an operation on incompatible types
using an identifier which has not been defined
accessing a list element, dictionary value or object attribute
which doesn’t exist
trying to access a file which doesn’t exist
Logical errors
Logical errors are the most difficult to fix. They occur when the
program runs without crashing, but produces an incorrect result.
The error is caused by a mistake in the program’s logic. You won’t
get an error message, because no syntax or runtime error has
occurred. You will have to find the problem on your own by
reviewing all the relevant parts of your code – although some tools
can flag suspicious code which looks like it could cause
unexpected behaviour.
Exercise 1
1. Find all the syntax errors in the code snippet above, and
explain why they are errors.
2. Find potential sources of runtime errors in this code snippet:
3. dividend = float(input("Please enter the dividend:
"))
4. divisor = float(input("Please enter the divisor: "))
5. quotient = dividend / divisor
6. quotient_rounded = math.round(quotient)
7. Find potential sources of runtime errors in this code snippet:
8. for x in range(a, b):
9. print("(%f, %f, %f)" % my_list[x])
10. Find potential sources of logic errors in this code
snippet:
11. product = 0
12. for i in range(10):
13. product *= i
14.
15. sum_squares = 0
16. for i in range(10):
17. i_sq = i**2
18. sum_squares += i_sq
19.
20. nums = 0
21. for num in range(10):
22. num += num
4
Handling exceptions
Until now, the programs that we have written have generally
ignored the fact that things can go wrong. We have have tried to
prevent runtime errors by checking data which may be incorrect
before we used it, but we haven’t yet seen how we can handle
errors when they do occur – our programs so far have just crashed
suddenly whenever they have encountered one.
All the runtime (and syntax) errors that we have encountered are
called exceptions in Python – Python uses them to indicate that
something exceptional has occurred, and that your program cannot
continue unless it is handled. All exceptions are subclasses of the
Exception class – we will learn more about classes, and how to
write your own exception types, in later chapters.
It is possible for one except clause to handle more than one kind of
error: we can provide a tuple of exception types instead of a single
type:
try:
dividend = int(input("Please enter the dividend: "))
divisor = int(input("Please enter the divisor: "))
print("%d / %d = %f" % (dividend, divisor,
dividend/divisor))
except(ValueError, ZeroDivisionError):
print("Oops, something went wrong!")
6
try:
dividend = int(input("Please enter the dividend: "))
divisor = int(input("Please enter the divisor: "))
print("%d / %d = %f" % (dividend, divisor,
dividend/divisor))
except ValueError:
print("The divisor and dividend have to be numbers!")
except ZeroDivisionError:
print("The dividend may not be zero!")
try:
divisor = int(input("Please enter the divisor: "))
except ValueError:
print("The divisor has to be a number!")
try:
print("%d / %d = %f" % (dividend, divisor,
dividend/divisor))
except ZeroDivisionError:
print("The dividend may not be zero!")
7
How an exception is handled
8
# with checks
n = None
while n is None:
s = input("Please enter an integer: ")
if s.lstrip('-').isdigit():
n = int(s)
else:
print("%s is not an integer." % s)
n = None
while n is None:
try:
s = input("Please enter an integer: ")
n = int(s)
except ValueError:
print("%s is not an integer." % s)
9
The else and finally statements
There are two other clauses that we can add to a try-except block:
else and finally. else will be executed only if the try clause
doesn’t raise an exception:
try:
age = int(input("Please enter your age: "))
except ValueError:
print("Hey, that wasn't a number!")
else:
print("I see that you are %d years old." % age)
We want to print a message about the user’s age only if the integer
conversion succeeds. In the first exception handler example, we
put this print statement directly after the conversion inside the try
block. In both cases, the statement will only be executed if the
conversion statement doesn’t raise an exception, but putting it in
the else block is better practice – it means that the only code inside
the try block is the single line that is the potential source of the
error that we want to handle.
10
try:
age = int(input("Please enter your age: "))
except ValueError:
print("Hey, that wasn't a number!")
else:
print("I see that you are %d years old." % age)
finally:
print("It was really nice talking to you. Goodbye!")
Exercise 2
11
The with statement
err is not a string, but Python knows how to convert it into one –
the string representation of an exception is the message, which is
exactly what we want. We can also combine the exception
message with our own message:
try:
age = int(input("Please enter your age: "))
except ValueError as err:
print("You entered incorrect age input: %s" % err)
12
Raising exceptions
We can raise our own ValueError if the age input is a valid integer,
but it’s negative. When we do this, it has exactly the same effect as
any other exception – the flow of control will immediately exit the
try clause at this point and pass to the except clause. This except
clause can match our exception as well, since it is also a
ValueError.
We can also write our own custom exception classes which are
based on existing exception classes – we will see some examples
of this in a later chapter.
13
Something we may want to do is raise an exception that we have
just intercepted – perhaps because we want to handle it partially in
the current function, but also want to respond to it in the code
which called the function:
try:
age = int(input("Please enter your age: "))
except ValueError as err:
print("You entered incorrect age input: %s" % err)
raise err
Exercise 3
Debugging programs
Syntax errors are usually quite straightforward to debug: the error
message shows us the line in the file where the error is, and it
should be easy to find it and fix it.
Logical errors are the most difficult to fix because they don’t cause
any errors that can be traced to a particular line in the code. All
that we know is that the code is not behaving as it should be –
sometimes tracking down the area of the code which is causing the
incorrect behaviour can take a long time.
14
It is important to test your code to make sure that it behaves the
way that you expect. A quick and simple way of testing that a
function is doing the right thing, for example, is to insert a print
statement after every line which outputs the intermediate results
which were calculated on that line. Most programmers intuitively
do this as they are writing a function, or perhaps if they need to
figure out why it isn’t doing the right thing:
def hypotenuse(x, y):
print("x is %f and y is %f" % (x, y))
x_2 = x**2
print(x_2)
y_2 = y**2
print(y_2)
z_2 = x_2 + y_2
print(z_2)
z = math.sqrt(z_2)
print(z)
return z
15
unnecessarily verbose. Here is what the function above
would normally look like:
def hypotenuse(x, y):
return math.sqrt(x**2 + y**2)
Debugging tools
There are some automated tools which can help us to debug errors,
and also to keep our code as correct as possible to minimise the
chances of new errors creeping in. Some of these tools analyse our
program’s syntax, reporting errors and bad programming style,
while others let us analyse the program as it is running.
These four utilities analyse code for syntax errors as well as some
kinds of runtime errors. They also print warnings about bad coding
style, and about inefficient and potentially incorrect code – for
example, variables and imported modules which are never used.
16
Pylint and PyChecker do import the code that they check, and they
produce more extensive lists of errors and warnings. They are used
by programmers who find the functionality of pyflakes to be too
basic.
pdb
def our_function():
bad_idea = 3 + "4"
pdb.run('our_function()')
17
Logging
Sometimes it is valuable for a program to output messages to a
console or a file as it runs. These messages can be used as a record
of the program’s execution, and help us to find errors. Sometimes a
bug occurs intermittently, and we don’t know what triggers it – if
we only add debugging output to our program when we want to
begin an active search for the bug, we may be unable to reproduce
it. If our program logs messages to a file all the time, however, we
may find that some helpful information has been recorded when
we check the log after the bug has occurred.
19