Error Handling - Learning to Program with Python 3 (basics)




Welcome to part 9 of the Python 3 basics series. Leading up to this point, we've been building a TicTacToe game while we learn to program. In the previous tutorial, we covered some ways one can go wrong using functions and variables outside of the function. In this tutorial, we're going to talk some more about other things that can go wrong.

A big part of programming is envisioning both how people will use your program legitimately as well as illegitimately. Not everyone is trying to hack you, some people just don't think the way you do and might try to do something that wont work. So let's start with the following code:

game = [[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]]


def game_board(game_map, player=0, row=0, column=0, just_display=False):
    print("   0  1  2")
    if not just_display:
        game_map[row][column] = player
    for count, row in enumerate(game_map):
        print(count, row)
    return game_map

I am going to continue with the method of re-defining the game we pass to our function. No, it's not needed in this case, but I think it's a better habit to be in, plus it's more readable. If you do not wish to do it this way, that's fine, I wont be offended!

Anyway, what happens when someone sees the grid, and, despite our labels, tries something like:

game = game_board(game, player=1, row=3, column=1)
Traceback (most recent call last):
  File "C:\Users\H\Desktop\python3-updated-series\part9.py", line 14, in 
    game = game_board(game, player=1, row=3, column=1)
  File "C:\Users\H\Desktop\python3-updated-series\part9.py", line 7, in game_board
    current_game[row][column] = player
IndexError: list index out of range
>>> 

Not only did this not work, the entire program just broke. We can only restart it. This is a crash. People do not like these! It might be your user's fault, but the user is going to be mostly mad at you right now.

As we program though, we can see this coming from a mile away. We can be confident a user is going to accidentally enter a value too big because they don't understand the rules, or maybe they just simply hit the wrong key, who knows why, but we can very confident that we can expect a mistake like this to happen. So what do we do? Well, we can handle errors with try and except. Basically we try to do something, but, if that fails, we run the exception.

We can do this very easily with:

def game_board(game_map, player=0, row=0, column=0, just_display=False):
    try:
        print("   0  1  2")
        if not just_display:
            game_map[row][column] = player
        for count, row in enumerate(game_map):
            print(count, row)
        return game_map
    except:
        print("A bad thing just happened.")
        return False

So we're just trying to run that block, but, if we cannot do it, we print out that something bad happened, and then we go ahead and let the function just return a False. You could also return a None, depending on how you want to handle that. Anyway, this works. If we run

game = game_board(game, player=1, row=3, column=1)
A bad thing just happened.
>>> 

Alright, nice. But what happened? We don't really know based on this error, it's unclear. As programmers, we can handle for very specific errors and handle those accordingly. We could print out something like asking for all of the things that we think could have gone wrong, but a user isn't going to read a book of reasons why your program sucks. In today's age, people have very little patience. A hard crash? You probably just lost that user forever. So, how can we turn a crash into something more like a helpful hint about the exact issue? Well, we can handle for exact issues. For example, if you look about, find the TitleCased name for the error. Usually, the error you care about is right at the end of the traceback. Sometimes, it's buried, however, if you've imported some other script, and something in there failed first. For now though, our program is simple and it's at the end:

IndexError: list index out of range

So, the type of error is an IndexError. It turns out, we can handle for this exact type of error!

    except IndexError:
        print("Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)")
        return False

All together now:

game = [[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]]


def game_board(game_map, player=0, row=0, column=0, just_display=False):
    try:
        print("   0  1  2")
        if not just_display:
            game_map[row][column] = player
        for count, row in enumerate(game_map):
            print(count, row)
        return game_map
    except IndexError:
        print("Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)")
        return False


game = game_board(game, player=1, row=3, column=1)
Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)
>>> 

Alright, now that's way more useful. Now, I am having trouble coming up with more ways that people will mess up this function, but, for example, what about:

game = game_board(game_board, player=1, row=3, column=1)

We're passing game_board instead, just to throw an error.

Traceback (most recent call last):
  File "C:\Users\H\Desktop\python3-updated-series\part9.py", line 18, in 
    game = game_board(game_board, player=1, row=3, column=1)
  File "C:\Users\H\Desktop\python3-updated-series\part9.py", line 8, in game_board
    current_game[row][column] = player
TypeError: 'function' object is not subscriptable
>>> 

So this one throws a TypeError, and still crashes our program. We could handle for every single type of error, but I am not sure this program really needs it yet. Instead, we can handle a general exception as well, which we can use instead for mostly debugging purposes. We'd like to specifically handle for errors we expect our users to cause, but then we also would like to handle for other errors we didn't expect with a general exception catch, so let's do that too. We can just add the following except statement under the previous one like:

    except Exception as e:
        print(str(e))
        return False

Full code now is:

game = [[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]]


def game_board(game_map, player=0, row=0, column=0, just_display=False):
    try:
        print("   0  1  2")
        if not just_display:
            game_map[row][column] = player
        for count, row in enumerate(game_map):
            print(count, row)
        return game_map
    except IndexError:
        print("Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)")
        return False
    except Exception as e:
        print(str(e))
        return False


game = game_board(game_board, player=1, row=3, column=1)
'function' object is not subscriptable
>>> 

A user isn't likely to understand that, but the program continues along. At least with the error we just created, a user wouldn't really be able to cause it anyway, I just wanted to show it for an example of a general exception. There are some more logical handlings that we can add to try/except statements (else and finally). We probably won't get to use them there, they really only make sense when we're doing some sort of operation like IO where an abrupt disconnect could cause trouble. Just know...they exist.

Alright, next, let's cover how we might start validating winners of the game.

The next tutorial:





  • Introduction to Python 3 (basics) - Learning to Program with Python 3
  • Tuples, Strings, Loops - Learning to Program with Python 3 (basics)
  • Lists and Tic Tac Toe Game - Learning to Program with Python 3 (basics)
  • Built-in Functions - Learning to Program with Python 3 (basics)
  • Indexes and Slices - Learning to Program with Python 3 (basics)
  • Functions - Learning to Program with Python 3 (basics)
  • Function Parameters and Typing - Learning to Program with Python 3 (basics)
  • Mutability Revisited - Learning to Program with Python 3 (basics)
  • Error Handling - Learning to Program with Python 3 (basics)
  • Calculating Horizontal Winner (tic tac toe) - Learning to Program with Python 3 (basics)
  • Vertical Winners - Learning to Program with Python 3 (basics)
  • Diagonal Winners - Learning to Program with Python 3 (basics)
  • Bringing Things Together - Learning to Program with Python 3 (basics)
  • Wrapping up Tic Tac Toe - Learning to Program with Python 3 (basics)
  • Conclusion - Learning to Program with Python 3 (basics)