Exception handling
When writing a program, we, more often than not, will
encounter errors. Error caused by not following the proper structure (syntax)
of the language is called syntax error or parsing error.
>>> if a
< 3
File "<interactive
input>", line 1
if a
< 3
^
SyntaxError:
invalid syntax
We can notice here that a colon is missing in the if statement.
Errors can also occur at runtime and these are called
exceptions. They occur, for example, when a file we try to open does not exist
(FileNotFoundError), dividing a number by zero (ZeroDivisionError), module we try to import
is not found (ImportError) etc. Whenever these type of runtime error
occur, Python creates an exception object. If not handled properly, it prints a
traceback to that error along with some details about why that error occurred.
>>> 1 / 0
Traceback
(most recent call last):
File "<string>",
line 301, in
runcode
File "<interactive
input>", line 1, in <module>
ZeroDivisionError:
division by zero
>>> open("imaginary.txt")
Traceback
(most recent call last):
File "<string>",
line 301, in
runcode
File "<interactive
input>", line 1, in <module>
FileNotFoundError: [Errno 2] No
such file or directory: 'imaginary.txt'
Python Built-in Exceptions
Illegal operations can raise exceptions. There are plenty
of built-in exceptions in Python that are raised when corresponding errors
occur. We can view all the built-in exceptions using the local() built-in functions as
follows.
>>> locals()['__builtins__']
This will return us a dictionary of built-in exceptions,
functions and attributes. Some of the common built-in exceptions in Python
programming along with the error that cause then are tabulated below.
Python Built-in Exceptions
|
|
Exception
|
Cause
of Error
|
AssertionError
|
Raised
when assert statement fails.
|
AttributeError
|
Raised
when attribute assignment or reference fails.
|
EOFError
|
Raised
when the input() functions hits end-of-file
condition.
|
FloatingPointError
|
Raised
when a floating point operation fails.
|
GeneratorExit
|
Raise
when a generator's close()method is called.
|
ImportError
|
Raised
when the imported module is not found.
|
IndexError
|
Raised
when index of a sequence is out of range.
|
KeyError
|
Raised
when a key is not found in a dictionary.
|
KeyboardInterrupt
|
Raised
when the user hits interrupt key (Ctrl+c or delete).
|
MemoryError
|
Raised
when an operation runs out of memory.
|
NameError
|
Raised
when a variable is not found in local or global scope.
|
NotImplementedError
|
Raised
by abstract methods.
|
OSError
|
Raised
when system operation causes system related error.
|
OverflowError
|
Raised
when result of an arithmetic operation is too large to be represented.
|
ReferenceError
|
Raised
when a weak reference proxy is used to access a garbage collected referent.
|
RuntimeError
|
Raised
when an error does not fall under any other category.
|
StopIteration
|
Raised
by next() function to indicate that
there is no further item to be returned by iterator.
|
SyntaxError
|
Raised
by parser when syntax error is encountered.
|
IndentationError
|
Raised
when there is incorrect indentation.
|
TabError
|
Raised
when indentation consists of inconsistent tabs and spaces.
|
SystemError
|
Raised
when interpreter detects internal error.
|
SystemExit
|
Raised
by sys.exit() function.
|
TypeError
|
Raised
when a function or operation is applied to an object of incorrect type.
|
UnboundLocalError
|
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
|
Raised
when a Unicode-related encoding or decoding error occurs.
|
UnicodeEncodeError
|
Raised
when a Unicode-related error occurs during encoding.
|
UnicodeDecodeError
|
Raised
when a Unicode-related error occurs during decoding.
|
UnicodeTranslateError
|
Raised
when a Unicode-related error occurs during translating.
|
ValueError
|
Raised
when a function gets argument of correct type but improper value.
|
ZeroDivisionError
|
Raised
when second operand of division or modulo operation is zero.
|
When an exception occurs in Python, it causes
the current process to stop and passes it to the calling process until it is
handled. If not handled, our program will crash. For example, if function
A
calls function B
which in turn calls function C
and an exception occurs in function C
. If it is not handled
in C
, the exception passes to B
and then to A
. If never handled, an error message is spit out and our
program come to a sudden, unexpected halt.
Catching Exceptions in Python
In Python, exceptions can be handled using a
try
statement. A critical operation which can
raise exception is placed inside the try
clause and the code
that handles exception is written in except
clause. It is up to us,
what operations we perform once we have caught the exception. Here is a simple
example.
# import module sys to get the type of exception
import sys
while True:
try:
x = int(input("Enter an integer: "))
r = 1/x
break
except:
print("Oops!",sys.exc_info()[0],"occured.")
print("Please try again.")
print()
print("The reciprocal of",x,"is",r)
Here is a sample run of this program.
Enter an integer: a
Oops! <class 'ValueError'> occured.
Please try again.
Enter an integer: 1.3
Oops! <class 'ValueError'> occured.
Please try again.
Enter an integer: 0
Oops! <class 'ZeroDivisionError'> occured.
Please try again.
Enter an integer: 2
The reciprocal of 2 is 0.5
In this program, we loop until the user enters
an integer that has a valid reciprocal. The portion that can cause exception is
placed inside
try
block. If no exception
occurs,except
block is skipped and normal flow continues.
But if any exception occurs, it is caught by the except
block. Here, we print the name of the
exception using ex_info()
function inside sys
module and ask the user to try again. We can
see that the values 'a' and '1.3' causes ValueError
and '0' causes ZeroDivisionError
.
Catching Specific Exceptions in Python
In the above example, we did not mention any
exception in the
except
clause. This is not a
good programming practice as it will catch all exceptions and handle every case
in the same way. We can specify which exceptions an except
clause will catch. A try
clause can have any
number of except
clause to handle them
differently but only one will be executed in case an exception occurs. We can
use a tuple of values to specify multiple exceptions in an except
clause. Here is an example pseudo code.
try:
# do something
pass
except ValueError:
# handle ValueError exception
pass
except (TypeError, ZeroDivisionError):
# handle multiple exceptions
# TypeError and ZeroDivisionError
pass
except:
# handle all other exceptions
pass
Raising Exceptions
In Python programming, exceptions are raised
when corresponding errors occur at run time, but we can forcefully raise it
using the keyword
raise
. We can also optionally pass in value to the
exception to clarify why that exception was raised.
>>> raise KeyboardInterrupt
Traceback (most recent call last):
...
KeyboardInterrupt
>>> raise MemoryError("This is an argument")
Traceback (most recent call last):
...
MemoryError: This is an argument
>>> try:
... a = int(input("Enter a positive integer: "))
... if a <= 0:
... raise ValueError("That is not a positive number!")
... except ValueError as ve:
... print(ve)
...
Enter a positive integer: -2
That is not a positive number!
try...finally
The
try
statement in Python can
have an optional finally
clause. This clause is
executed no matter what, and is generally used to release external resources.
For example, we may be connected to a remote data center through the network or
working with a file or working with a Graphical User Interface (GUI). In all
these circumstances, we must clean up the resource once used, whether it was
successful or not. These actions (closing a file, GUI or disconnecting from
network) are performed in the finally
clause to guarantee
execution.
Here is an example to illustrate this.
try:
f = open("test.txt",encoding = 'utf-8')
# perform file operations
finally:
f.close()
This type of construct makes sure the file is
closed even if an exception occurs.
Users can define their own exception by creating a new class
in Python. This exception class has to be derived, either directly or
indirectly, from
Exception
class. Most of the
built-in exceptions are also derived form this class.
>>> class CustomError(Exception):
... pass
...
>>> raise CustomError
Traceback (most recent call last):
...
__main__.CustomError
>>> raise CustomError("An error occurred")
Traceback (most recent call last):
...
__main__.CustomError: An error occurred
Here, we have created a user-defined exception
called
CustomError
which is derived from the Exception
class. This new exception can be raised, like
other exceptions, using the raise
statement with an
optional error message.
When we are developing a large Python program,
it is a good practice to place all the user-defined exceptions that our program
raises in a separate file. Many standard modules do this. They define their
exceptions separately as
exceptions.py
or errors.py
(generally but not always).
User-defined exception class can implement
everything a normal class can do, but we generally make them simple and
concise. Most implementations declare a custom base class and derive others
exception classes from this base class. This concept is made clearer in the
following example.
Example of User Defined Exception
In this example, we will illustrate how
user-defined exceptions can be used in a program to raise and catch errors.
This program will ask the user to enter a number until they guess a stored
number correctly. To help them figure it out, hint is provided whether their guess
is greater than or less than the stored number.
# define Python user-defined exceptions
class Error(Exception):
"""Base class for other exceptions"""
pass
class ValueTooSmallError(Error):
"""Raised when the input value is too small"""
pass
class ValueTooLargeError(Error):
"""Raised when the input value is too large"""
pass
# our main program
# user guesses a number until he/she gets it right
# you need to guess this number
number = 10
while True:
try:
i_num = int(input("Enter a number: "))
if i_num < number:
raise ValueTooSmallError
elif i_num > number:
raise ValueTooLargeError
break
except ValueTooSmallError:
print("This value is too small, try again!")
print()
except ValueTooLargeError:
print("This value is too large, try again!")
print()
print("Congratulations! You guessed it correctly.")
Here is a sample run of this program.
Enter a number: 12
This value is too large, try again!
Enter a number: 0
This value is too small, try again!
Enter a number: 8
This value is too small, try again!
Enter a number: 10
Congratulations! You guessed it correctly.
Here, we have defined a base class called
Error
. The other two
exceptions (ValueTooSmallError
and ValueTooLargeError
) that are actually raised by our
program are derived from this class. This is the standard way to define
user-defined exceptions in Python programming, but you are not limited to this
way only.
No comments:
Post a Comment