Enumerations and 5-Way Variation
00:00 In the previous lesson, you learned how to determine who is the winner in rock paper scissors. In this lesson, I’ll show you how to use enumerations to reduce the chance of errors in your code, and then add a Spock to your game.
There are a few exceptions but, generally, hard-coded strings and numbers are considered bad practice in programming. Looking back at the last program, there are six instances of the word
"rock", six of
"paper", and six of
"scissors". One little spelling error in any one of those six cases and you’ll have a bug in your code. What’s worse, even if your code is bug free, your user has to keep typing
"scissors". It could get frustrating quickly.
Python supports the concept of an enumeration, or enum for short. This is a way of grouping symbols or values together. For our code, the
"scissors" are all choices that the user can make. An enum grouping those together makes sense.
There are several different types of enums. The one I’ll show you is
IntEnum. This uses integers to represent each value in the enumeration.
This is the
numbers.py script. I started with the concept shown in
beats.py, but changed the choices into enumerations. The first change required is to import the
IntEnum from the
You create enums by building classes that inherit from an enumeration class. Here, I’ve created the
Choice class by inheriting from
Each declaration inside of this enumeration must have an integer associated with it. I’m using
0 for rock,
1 for paper, and
2 for scissors.
BEATS dict needs to be updated. It used to hard-code the rock, paper, and scissors values as strings. Here, I’ve replaced them with the attributes of the
You access a value from an enum by specifying it with dot notation. The compiler will replace
Choice.Rock with the number
A similar fix needs to be done to update the
MESSAGES dict. With these changes in place, any spelling error in the word scissors will result in a compiler error. Let me just scroll down to the bottom.
02:25 The code for requesting input from the user has changed a little bit. You might expect that, given that it used to be looking for strings and now it’s looking for an integer.
The entire input section is wrapped in a
except block. The input of an invalid choice will now result in a
ValueError being raised.
Let’s go through it step by step. Line 46 still asks the user for input. Note the
CHOICES_STR (choices string) variable inside the input prompt. Instead of hard-coding the choices, this string is determined dynamically based on the contents of the
Choice enum. Let me scroll back up to the top to show you where this is done.
Line 10 does a list comprehension. This is a short-form for creating a list by looping over some values. In this case, all the possible choices in the
Choice enumeration are iterated over and for each choice, a string is built.
The string contains the name of the choice, square brackets, and the number of the choice inside of the brackets. The all-caps
CHOICES list will have three strings inside of it:
03:36 Line 11 takes the list created on line 10 and turns it into a single string with the values separated by commas. This string is a readable version of the choices the user will be presented with.
03:49 With me so far? Good. Let’s go back down to the input code.
Line 46 prompted the user with the choices string. Line 47 then creates a
Choice enumeration object based on what the user typed in. Two things can go wrong here. One, the thing the user typed in might not convert into an integer, and two, the user might type in an integer that isn’t a valid choice.
Both of these situations raise a
int() function raises a
ValueError if it can’t convert the string it was given into an integer, and the
IntEnum raises a
ValueError if you try to create a
Choice object with a bad value. In either of these cases, the
except clause will run. Line 49 displays an error message, and then line 50 causes the code to continue execution from the top of the loop, giving the user a chance to type something else in.
The random choice the computer makes has to change as well. Previously, you used the
choice() function from the
random module that randomly chose an item from a list. Now that you’re using an
IntNum, it is better to take advantage of a different function from
randint() function returns a random integer between the two values it is given, inclusively. You need a random choice between
0, rock, and
Enum objects support the use of the
len() function. By doing it this way, you don’t have to hard-code the last item in the enum, you just use it.
Remember, though, collections of things in Python are usually zero-indexed. In this case,
len() will return
3. There are three things in the enum. Subtract
1 from the length to get the appropriate upper boundary for the random integer. Line 53 constructs a
Choice object using the random value just determined.
As before, the values for the user and computer are passed to
show_winner(). They used to be strings, but now they are
Choice objects, so the
show_winner() function is going to need some modifications.
Let me just scroll up to it. Previously, the arguments to
show_winner() were expected to be strings. Now, they’re
Choice objects, so a few things have to change inside of here.
Line 27 shows the first example of this. You can get the text representation of an enum by accessing the
.name property on it. What used to be just
user_choice is now
user_choice.name to get the text version of the number the user typed. As the enum named each value with capital letters, I’m also calling
.lower() on the result.
There are several other cases inside of
show_winner() where this kind of thing has to be done as well. All of them are places where user or computer choices were displayed, but the change is the same as line 27.
06:42 With all that done, let’s go see this in action.
06:50 A bunch of code was touched in order to make it safer. Not much has changed from the user’s perspective, except now you’re choosing numbers instead of typing out words.
Let me pick
2 for scissors. Much easier to type.
And to show the error conditions, I’ll type
5? That’s good. The error conditions work.
07:19 Most of the code changes in the last two scripts were kind of neutral. The code was a bit cleaner and a little shorter, but not by much. Where these changes become important is if you want to make the game more complicated.
07:32 There’s a five-way variation on rock paper scissors called rock, paper, scissors, lizard, Spock. Each item in this version of the game beats and can be beat by two other items.
Think back to your first version of
show_winner() and that large if-then-else block. Encoding the lizard Spock version would be much worse, wouldn’t it?
The switch to enumerations makes life easier as well. To get the new version going, all you need to do is update the
Choice enumeration with lizard and Spock and update the
This is inside of
spock.py. The code is built on
numbers.py but with the new lizard and Spock changes. The first thing to do is add lizard and Spock to the
08:27 Then, update the game rules, encoding how each of the five choices beats out two of the other choices.
08:38 Make a similar set of changes for all of our verbs.
And then one tiny change, due to my anal retention. I’ve removed the call to
.lower() here on line 38 because Spock is a proper name. It offends me less to have capital-R
"Rock" here than it does to have small-s
"spock". If you wanted to get fancy, you could add a bit of conditional code to deal with both, but I went with lazy instead. With everything in place, you’re ready to go.
09:12 Let me just run this. Let’s start with lizard.
09:22 You can see how structuring your code correctly can make certain kinds of changes far simpler. Writing a program is always a trade-off. You can do clever things and possibly spend a lot of time for little value, or you can do obvious things and often make your life painful later when you want to make changes. The more you code, the more you’ll get used to these kinds of decisions and the choice will become more obvious with experience. Next up, I’ll wrap up the course with a quick summary.
Become a Member to join the conversation.