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, ingame = 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, ingame = 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.