Get up to 80 % extra points for free! More info:

Lesson 1 - Exceptions in Python

In this Python course, we'll focus on working with files. However, before we can start writing and reading data files, we should figure out how to handle program error states, which will occur a lot when working with files.

An error can often occur in our program. I don't mean an error due to the fact that the program was poorly written, we are able to avoid such errors well. In general, these are mainly errors that are caused by the input/output operations. We can abbreviate "input/output" as IO. This is, for example, user input from the console, a file, output to a file, printer etc. In principle, there's the user who can give us invalid input, a non-existent or invalid file, disconnect the printer, and so on. However, we'll not let the program crash with an error, on the contrary, we'll treat vulnerabilities in the program and alert the user to the given fact.

Active Error Handling

The first error handling option is called "active". In the program, we map all vulnerabilities and treat them with conditions. Zero division is usually used as a textbook example. Imagine a program that uses a Math class that has a quotient() method. The class might look like this:

class Math():

    def quotient(self, a, b):
        return a / b

We'll now use the class as follows:

print("Enter a divident and divisor to calculate the quotient:")
a = int(input())
b = int(input())
print(Math().quotient(a, b))

If the user enters the numbers 12 and 0 in the program, the program will crash with an error because it cannot divide by zero. We actively treat the error with a simple condition in the program:

print("Enter a dividend and divisor to calculate the quotient:")
a = int(input())
b = int(input())
if b != 0:
    print(Math().quotient(a, b))
else:
    print("Cannot divide by zero.")

Now we have to make sure that we don't input a zero in the second parameter every time we use the method. Imagine the method processed 10 parameters and we used it several times in the program. It'd certainly be very difficult to sanitize all uses of this method.

The solution could be to insert the check directly into the method. However, we have a new problem here: What value do we return when the 2nd parameter is zero? We need a value from which we know that the calculation didn't work correctly. This is a problem when we choose, for example, zero, we don't know whether, for example, 0/12 is an incorrect calculation or not. We can't tell whether 0 indicates a result or an error. We can't do it using negative numbers either. Parsing values is the 2nd classic example of vulnerable user input. Other are file operations, where the file might not exist, we might not have rights to it, it might be used with other program and so on.

Passive Error Handling

Especially when the operation is more complex and it'd be too difficult to treat all possible error conditions, exceptions come to play, the passive error handling. We don't need to be interested in the internal logic in the method we call. We'll try to run the dangerous part of the code in "protected mode". This mode is slightly slower and differs in that if an error occurs, we have the option to catch it and prevent the program from crashing. We speak of the error here as of an exception. We use the try - except blocks:

try:
    pass
except:
    pass

We place the dangerous part of the code in the try block. If an error occurs in the try block, its execution is interrupted and the program goes to the except block. If everything goes well, the try block is executed fully and the except block is skipped. Let's try the situation in our previous example:

try:
    print(Math().quotient(a, b))
except:
    print("Error during division.")

The code is simpler in that we don't have to treat all the vulnerabilities and think about what could go wrong. We only wrap the risky code with a try block and all errors are caught in except. Of course, we only place what is absolutely necessary in the try - except block, not the whole program :)

So now we know how to handle situations where the user enters an input that could cause an error. It doesn't have to be just file operations, exceptions have a very wide range of uses. We can write our program in such a way that it can't be easily crashed by the user.

Using Exceptions When Working With Files

As already mentioned, file operations can throw many exceptions, so we always work with files in a try - except block. There are also several other constructs that we can use with exceptions.

Finally

We can add a 3rd block to the try - except called finally. It always runs whether or not an exception has occurred. Imagine the following method to save settings:

def saveSettings():
    try:
        f = open("file.dat", "w")
        f.write("Some settings...")
        f.close() # we need to close the file handler
    except:
        print("Error writing the file.")

This method tries to open the file and write some settings to it. We must then close the open file. A message is printed to the console in case of an error. However, listing errors directly in a method is ugly, we already know that methods and objects in general should only perform logic, and communication with the user is done by the function which calls them. So let's return True / False depending on whether the operation was successful or not:

def saveSettings():
    try:
        f = open("file.dat", "w")
        f.write("Some settings...")
        return True
    except:
        return False
    f.close()

At first glance, it looks like the file always closes. However, the whole code is in a method in which we call return. As we know, return terminates the method and nothing after it is executed.

Here, the file would always remain open and closing would no longer take place. As a result, the file could then be inaccessible. If we close the file in the block finally, it'll always be executed. Python remembers that the try - except block contained finally and calls the finally block even after leaving the except or try block:

def saveSettings():
    try:
        f = open("file.dat", "w")
        f.write("Some settings...")
        return True
    except:
        return False
    finally:
        f.close()

It'd be best to leave out the except block altogether and let the method throw the exception. We'll rely on whatever called the method that threw the exception to deal with it, instead of the method itself. That's better, since we save the return value of the method (which can then be used for something else) and the code is simplified for us:

def saveSettings():
    try:
        f = open("file.dat", "w")
        f.write("Some settings...")
    finally:
        f.close()
}

The file always closes in the code above, even if something goes wrong during writing, e.g. the storage space runs out. We'd now call the method like this:

try:
    saveSettings()
except:
    print("Failed to save settings.")

We'll now show you how to simplify the whole situation even more. We use the with construct.

The with Construct

Python greatly simplifies working with instances of classes to read and write to files or, in general, classes that need to perform any cleanup work. The above block can be written using the with notation, which replaces the try and finally blocks. A huge advantage is that Python generates the finally block itself and ensures that the instance closes the file. Using with, the saveSettings() method would look like this:

def saveSettings():
    with open("file.dat", "w") as f:
        f.write(object)
}

We can see that the code has become extremely simple, even though it does essentially the same thing. When calling the method, we'll use the try - except block again.

Remember that with only replaces try - finally, not except !. The method in which with is used must still be called in the try-except block.

Now we've reached exactly where we needed to. In all the following tutorials, we'll use the with construct for all file manipulations. The code will be simpler and we'll never forget to close the file.

We'll return to exceptions in the future to show you how to catch only some of the types of exceptions, ready-made exception classes we can use in our programs, and how to create our own exception. Now, however, I just wanted to explain the necessary minimum for working with files while not confusing you with complex constructions.:)

In the next lesson, Introduction to Working With Files in Python, we'll look at how to work with file write rights in Windows and try the first few file operations.


 

All articles in this section
Files and I/O in Python
Skip article
(not recommended)
Introduction to Working With Files in Python
Article has been written for you by Shift
Avatar
User rating:
No one has rated this quite yet, be the first one!
As an author I'm interested mainly in industry automation. I'm also writing music and literature. In my spare time I'm working on a few games in Game Maker Studio.
Activities