Handling exceptions

by Alex
Handling exceptions

Exception handling in Python 3 is a powerful mechanism for handling program exceptions. It allows you to avoid termination due to sudden and unforeseen errors. This feature is implemented in the Python programming language by using special syntax structures that allow you to catch and then handle exceptions to make sure that the program always executes the specified algorithm.

What are exceptions?

Developing a program in any language is quite often connected with the occurrence of various kinds of errors that prevent obtaining the desired result. As a rule, not all of them lead to immediate termination of the program: some errors can be fixed at compilation time, some others can stay unnoticed for a long time and some others do not depend on the programmer and occur only in particular situations. The following types of errors can be distinguished:

  • Syntactic – occur due to syntactic errors in the code;
  • Logical – appear due to logical inaccuracies in the algorithm;
  • Exceptions – caused by incorrect actions of the user or the system.

Syntax errors are the result of non-compliance with the common norms of the language, for example, skipping the parenthesis in the intended place or incorrect writing of the function name. Such errors are successfully caught by the compiler, which immediately informs the user of the problem in the written code. The following example shows what happens if double quotes are omitted when creating a string literal:

print("Hello World!)

After an attempt to start it, an error text will be shown:

File "main.py", line 1
    print("Hello World!)
                       ^
SyntaxError: EOL while scanning string literal
Logical errors are considered more difficult to catch, because they are not detected either at compile time, nor at runtime. Usually they are caused by certain flaws in the logic of the written algorithm and thus the user does not get the desired result. Shown below is a function, which should calculate the average of two numbers, but it doesn't do it due to an incorrect formula:
def avg(a, b):
    return a + b / 2
print(avg(10, 20))

20

Exceptions are another kind of error, which appear depending on the presence of circumstances that change the execution of the program. An example of this would be entering an invalid value or missing a file. As a rule, all available types of exceptions become visible at runtime. The following example shows the impossibility of division by zero operation:

print(10 / 0)

After trying to run it, it will print:

Traceback (most recent call last):
File "main.py", line 1, in <module>
print(10 / 0)
ZeroDivisionError: division by zero
As you can see from the program's result, division by zero causes an exceptional situation that leads to a crash and an error message on the screen. In this case the file and the number of the code line where the ZeroDivisionError exception was thrown will be displayed. Below is a brief description of the problem.

Exception catching

To prevent an exception from causing a sudden program termination it is worth handling. There is a special mechanism preventing all unexpected situations for this purpose. It will help to avoid failures in the work of the written algorithm by extending its possibilities allowing to act according to the new circumstances. But first let’s try to run a program which will try to open a plain text file:

print("Program started")
print("Opening file...")
f = open("data.txt")
print("Program finished")

After launching, it will print:

Program started
Opening file...
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    f = open("data.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'

Since no file with that name was found on the hard drive, the program threw a FileNotFoundError exception, reporting a problem with the I/O. The last line of code, which denotes program termination, was not displayed on the screen. This means that not all of the operations provided by the algorithm were executed due to the exception that occurred. Consider an example in Python of the try-except construct. This allows us to handle an exceptional situation without having to terminate an already running program:

print("Program started")
try:
    print("Opening file...")
    f = open("data.txt")
except:
    print("File not found!")
print("Program finished")

Program started
Opening file...
File not found!
Program finished

As you can see from the above code, the try block contains dangerous code, which can lead to an error (the missing file), and the except block includes instructions, which should be executed if the problem occurs. Now if the needed file hasn’t been found, the program won’t quit, as the last output of the print function will attest.

Multiple except blocks

There can be several exceptions, depending on what type of exception needs to be handled. As a rule, more specific cases are handled first, and then the general ones:

print("Program started")
try:
    print("Opening file...")
    f = open("data.txt")
except FileNotFoundError:
    print("File not found!")
except Exception:
    print("Something went wrong!")
print("Program finished")

Program started
Opening file...
File not found!
Program finished

Nested blocks and else

The try-except blocks can be nested to allow more flexible exception handling. The following example demonstrates an attempt to open a text file and write a certain string into it. A separate try block is used for each purpose.

This example also uses the else construct, which is executed if no exceptions occur in the code.

In this case, else is thrown if the write operation succeeds. By default the file is opened in text mode. So we will use mode “w” to open the file. In this mode the file is opened for writing. If the file didn’t exist, a new one is created; if it did exist, it is overwritten.

print("Program started")
try:
    print("Opening file...")
    f = open("data.txt", "w")
    try:
        print("Writing to file...")
        f.write("Hello World!")
    except Exception:
        print("Something went wrong!")
    else:
        print("Success!")
except FileNotFoundError:
    print("File not found!")
print("Program finished")

Program started
Opening file...
Writing to file...
Success!
Program finished

finally

There are situations when some important action needs to be done, regardless of whether an exception is thrown or not. For this purpose, a finally block is used, containing a set of instructions that should be executed anyway. The following example improves the previous program by adding the ability to close a text file:

print("Program started")
try:
    print("Opening file...")
    f = open("data.txt", "w")
    try:
        print("Writing to file...")
        f.write("Hello World!")
    except Exception:
        print("Something went wrong!")
    else:
        print("Success!")
    finally:
        print("Closing file...")
        f.close()
except FileNotFoundError:
    print("File not found!")
print("Program finished")

Program started
Opening file...
Writing to file...
Success!
Closing file...
Program finished

This approach to handling text files can sometimes seem too complex, as the code to implement it looks unwieldy. The with/as construct exists specifically for this purpose, allowing some methods to be automated, such as closing the file with the corresponding object. This reduces the length of the code you write:

print("Program started")
try:
    print("Writing to file...")
    with open("data.txt", "w") as f:
        f.write("Hello World!")
except Exception:
    print("Something went wrong!")
print("Program finished")

Program started
Writing to file...
Program finished

As in all previous cases, a program that is able to correctly handle all occurring exception types always ends naturally, without interrupting the specified algorithm. This greatly increases the comfort and safety of its use.

Exception management

Python allows you to create custom exceptions. Also consider program logging.

Custom exceptions

Typically exceptions are raised automatically in right situations, but Python allows you to raise them yourself. For this use keyword raise. After raising it, a new object of type Exception must be created, which can be handled by a simple try-except statement, as in this example:

print("Program started")
try:
    raise Exception("User Exception!")
except Exception as e:
    print(str(e))
print("Program finished")

Program started
User Exception!
Program finished

To define your own exception type, you need to create a new class that inherits from the base Exception type. It allows to run special types of exceptions in situations when user’s behavior does not fit the program algorithm. In the Exception constructor specify the text of the exception. After it was triggered and intercepted you can retrieve it with str. The following code shows the process of generating a NegativeAge exception, which prevents the user from entering a negative age. Thus, the screen generates the corresponding error:

class NegativeAge(Exception):
    pass
print("Program started")
try:
    age = int(input("Enter your age: "))
    if age < 0:
        raise NegativeAge("Exception: Negative age!")
    print("Success!")
except NegativeAge as e:
    print(e)
print("Program finished")

Program started
Enter your age: -18
Exception: Negative age!
Program finished

Logging

Python uses a log library to print out special messages that don’t affect the running of the program. To use it, you must import it at the top of the file. There are several types of logs, which can be shown using the command defined in the second line of code in the following example program:

import logging

logging.basicConfig(level = logging.DEBUG)
logging.debug(“Debug message!”)
logging.info(“Info message!”)
logging.warning(“Warning message!”)
logging.error(“Error message!”)
logging.critical(“Critical message!”)

DEBUG:root:Debug message!
INFO:root:Info message!
WARNING:root:Warning message!
ERROR:root:Error message!
CRITICAL:root:Critical message!

As you can see from the program’s output, different types of messages are available to the user, such as warning, information or error. These are usually used in software development so that you don’t have to display extra text in the console. With logging in Python, exceptions can also be logged. Usually logging is written to a file, let’s set it as log.txt. The INFO level specifies that messages from levels below (in this case debug) will not be reflected in the log. Python allows us to try-except for an error text and write it down:

import logging

logging.basicConfig(filename=”log.txt”, level = logging.INFO)
try:
print(10 / 0)
except Exception as e:
logging.error(str(e))
A message line will be added to the log .txt about the type of exception that was triggered, “ERROR:root:division by zero”.

Exception Hierarchy

The Python programming language has a strict exception hierarchy. At its apex is BaseException which includes all exceptions that exist:

  • SystemExit – occurs when exiting the program with sys.exit;
  • KeyboardInterrupt – indicates an interruption of the program by the user;
  • GeneratorExit – appears when the close method of the generator object is called;
  • Exception – represents a set of common non-system exceptions.

The list of non-system exceptions that the Exception class contains is given in the following table. All of them are relevant to the current version of Python 3.

Name Characteristics
ArithmeticError Arithmetic errors are generated by arithmetic errors (floating point operations, number variable overflow, division by zero)
AssertionError It appears because of the false expression in the assert
AttributeError It appears in the cases when the necessary attribute of the object is absent
BufferError Indicates that a buffer operation cannot be performed
EOFError Appears when the function failed to read the end of the file
ImportError Reports an unsuccessful import of a module or attribute
LookupError Informs about an invalid index or key in an array
MemoryError Occurs in a situation where the available memory is not enough
NameError Indicates an error that occurs when searching for a variable with the right name
NotImplementedError Warns that abstract methods must be necessarily overridden in derived classes
OSError Includes system errors (no access to the desired file or directory, search problems with processes)
ReferenceError Is caused by an attempt to access an attribute with a weak reference
RuntimeError Reports an exception that does not classify
StopIteration Occurs during the next function if there are no elements
SyntaxError Represents a set of syntax errors
SystemError Is caused by internal system errors
TypeError Indicates that an operation could not be performed on a
UnicodeError Indicates an invalid character encoding in the program
ValueError Occurs when an incorrect value is received for a variable
Warning Indicates a warning

Conclusion

Exceptions are a type of data in their own right through which the program informs the user about various errors. Exception handling allows you to change the program’s execution scenario to account for circumstances that somehow block its normal operation. The Python programming language provides special syntax constructs for convenient exception handling. In addition, it has a large library of ready-made exceptions, as well as the ability to create your own.

Related Posts

LEAVE A COMMENT