Learn Python in One Day and Learn It Well (2015)

Part 1: myPythonFunctions.py

We will be writing two files for our programs. The first file is myPythonFunctions.py and the second is mathGame.py. Part 1 will focus on writing the code for myPythonFunctions.py.

To start, let’s first create the file myPythonFunctions.py. We’ll be defining three functions in this file.

Exercise 1: Importing Modules

We need to import two modules for myPythonFunctions.py: the random module and the os module.

We’ll be using the randint() function from the random module. The randint() function generates a random integer within the range provided by us. We’ll use that to generate numbers for our questions later.

From the os module, we’ll be using the remove() and rename() functions.

Try importing these two modules.

Exercise 2: Getting the User’s Score

Here we’ll define our first function. Let’s call it getUserPoint(). This function accepts one parameter, userName. It then opens the file ‘userScores.txt’ in ‘r’ mode.

userScores.txt looks something like this:

Ann, 100

Benny, 102

Carol, 214

Darren, 129

Each line records the information of one user. The first value is the user’s username and the second is the user’s score.

Next, the function reads the file line by line using a for loop. Each line is then split using the split() function (refer to Appendix A for an example on using the split() function).

Let’s store the results of the split() function in the list content.

Next, the function checks if any of the lines has the same username as the value that is passed in as the parameter. If there is, the function closes the file and returns the score beside that username. If there isn’t, the function closes the file and returns the string ‘-1’.

Clear so far? Try coding the function.

Done?

Now we need to make some modifications to our code. When opening our file previously, we used the ‘r’ mode. This helps to prevent any accidental changes to the file. However, when opening a file in ‘r’ mode, an IOError occurs if the file does not already exist. Hence when we run the program for the first time, we’ll end up with an error since the file userScores.txt does not exist previously. To prevent this error, we can do either of the following:

Instead of opening the file in ‘r’ mode, we can open it in ‘w’ mode. When opening in ‘w’ mode, a new file will be created if the file does not exist previously. The risk with this method is we may accidentally write to the file, which results in all previous content being erased. However, since our program is a small program, we can check through our code carefully to prevent any accidental writing.

The second method is to use a try, except statement to handle the IOError. To do that, we need to put all our previous codes in the try block, then use except IOError: to handle the ‘File not found’ error. In the except block, we’ll inform users that the file is not found and then proceed to create the file. We’ll use the open() function with ‘w’ mode to create it. The difference here is we use the ‘w’ mode only when the file is not found. Since the file does not exist initially, there is no risk of erasing any previous content. After creating the file, close the file and return the string “-1”.

You can choose either of the above methods to complete this exercise. The answer provided uses the second method. Once you are done, let’s move on to Exercise 3.

Exercise 3: Updating the User’s Score

In this exercise, we’ll define another function called updateUserPoints(), which takes in three parameters: newUser, userName and score.

newUser can either be True or False. If newUser is True, the function will open the file userScores.txt in append mode and append the user’s userName and score to the file when he or she exits the game.

if newUser is False, the function will update the user’s score in the file. However, there is no function in Python (or most programming languages for that matter) that allows us to update a text file. We can only write or append to it, but not update it.

Hence, we need to create a temporary file. This is a fairly common practice in programming. Let’s call this file userScores.tmp and open it in ‘w’ mode. Now, we’ll need to loop through userScore.txt and copy the data line by line to userScores.tmp. However, before copying, we’ll check if the userName on that line is the same as the one provided as the parameter. If it is the same, we’ll change the score to the new score before writing it to the temporary file.

For instance, if the parameters provided to the function are False, ‘Benny’ and ‘158’ (i.e. updateUserPoints(False, ‘Benny’, ‘158’)), the table below shows the difference between the original userScores.txt and the new userScores.tmp.

userScores.txt

Ann, 100

Benny, 102

Carol, 214

Darren, 129

userScores.tmp

Ann, 100

Benny, 158

Carol, 214

Darren, 129

After we finish writing to userScore.tmp, we’ll close both files and delete userScores.txt. Finally, we’ll rename userScores.tmp to userScores.txt.

Clear? Try coding it...

Exercise 4: Generating the Questions

We’ve now come to the most important part of the program, generating the mathematical questions. Ready?

To generate the questions, let’s first declare three variables: two lists and one dictionary.

We shall name the two lists operandList and operatorList.

operandList should store five numbers, with 0 as their initial values. operatorList should store four strings, with ‘ ’ as their initial values.

The dictionary consists of 4 pairs, with integers 1 to 4 as the dictionary keys, and “+”, “-”, “*”, “**” as the data. Let’s call this operatorDict.

[Exercise 4.1: Updating operandList with Random Numbers]

First we need to the replace the initial values of our operandList with random numbers generated by the randint() function.

The randint() takes in two parameters, start and end, and returns a random integer N such that start <= N <= end.

For instance, if randint(1, 9) is called, it’ll randomly return an integer from the numbers 1, 2, 3, 4, 5, 6, 7, 8, 9.

To update our operandList variable with random numbers, we can do this one by one since operandList only has five members. We can write

operandList[0] = randint(1, 9)

operandList[1] = randint(1, 9)

operandList[2] = randint(1, 9)

operandList[3] = randint(1, 9)

operandList[4] = randint(1, 9)

Each time randint(1, 9) is called, it’ll randomly return an integer from the numbers 1, 2, 3, 4, 5, 6, 7, 8, 9.

However, this is not the most elegant way of updating our operandList. Imagine how cumbersome it’ll be if operandList has 1000 members. The better alternative is to use a for loop.

Try using a for loop to accomplish the same task.

Done? Great!

[Exercise 4.2: Updating operatorList with Mathematical Symbols]

Now that we have the numbers to operate on, we need to randomly generate the mathematical symbols (+, -, *, **) for our questions. To do that, we’ll use the randint() function and the operatorDict dictionary.

randint() will generate the dictionary key, which will then be mapped to the correct operator using the operatorDict dictionary. For instance, to assign the symbol to operatorList[0], we write

operatorList[0] = operatorDict[randint(1, 4)]

Similar to Exercise 4.1, you should use a for loop to complete this task. However, there is one problem that makes this exercise harder than Exercise 4.1.

Recall that in Python, ** stands for exponent (i.e. 2**3 = 2^3)?

The problem is, when we have two consecutive exponent operators in Python, such as 2**3**2, Python interprets it as 2**(3**2) instead of (2**3)**2. In the first case, the answer is 2 to the power of 9 (i.e. 29) which is 512. In the second case, the answer is 8 to the power of 2 (i.e. 82) which is 64. Hence when we present a question like 2**3**2, the user will get the answer wrong if he interprets it as (2**3)**2.

To prevent this problem, we’re going to modify our code so that we do not get two consecutive ** signs. In other words, operatorList = [‘+’, ‘+’, ‘-’, ‘**’] is fine but operatorList = [‘+’, ‘-’, ‘**’, ‘**’] is not.

This exercise is the hardest among all the exercises. Try coming up with a solution to prevent two consecutive ** signs. Once you are done, we can proceed to Exercise 4.3.

Hint: If you are stuck, you can consider using an if statement within the for loop.

[Exercise 4.3: Generating a Mathematical Expression]

Now that we have our operators and operands, we are going to try to generate the mathematical expression as a string. This expression users the five numbers from our operandList and the four mathematical symbols from our operatorList to form a question.

We have to declare another variable called questionString and assign the mathematical expression to questionString. Examples of questionString include

6 – 2*3 – 2**1

4 + 5 – 2*6 + 1

8 – 0*2 + 5 – 8

Try to generate this expression yourself.

Hint: You can use a for loop to concatenate the individual substrings from operandList and operatorList to get the mathematical expression.

[Exercise 4.4: Evaluating the Result]

We should now have a mathematical expression as a string, assigned to the variable questionString. To evaluate the result of this expression, we’re going to use a brilliant built-in function that comes with Python, eval().

eval() interprets a string as a code and executes the code. For instance, if we write eval(“1+2+4”), we’ll get the number 7.

Hence to evaluate the result of our mathematical expression, we pass in questionString to the eval() function and assign the result to a new variable named result.

This exercise is pretty straight forward and can be completed in one step.

[Exercise 4.5: Interacting with the User]

Finally, we’re going to interact with our user. In this exercise, we’ll be doing a few things:

Step 1: Displaying the question to the user

Step 2: Prompting the user for an answer

Step 3: Evaluating the answer, displaying the appropriate message and returning the user’s score.

For step 1, we need to use a built-in function for manipulating strings. As mentioned earlier, in Python, the ** symbol stands for exponent. That is, 2**3 = 8. However, to most users, ** has no meaning. Hence if we display a question as 2**3 + 8 -5, the user will likely be confused. To prevent that, we’ll replace any ** symbol in questionString with the ^ symbol.

To do that, we’ll use the built-in function replace(). Using it is pretty straightforward, just write questionString = questionString.replace("**", "^"). Now you can print the resulting expression to the user.

For step 2, you can use the input() function to accept user input.

For step 3, you should use an if statement to evaluate the answer and display the correct message. If the user gets it correct, we’ll compliment the user and return the value 1. If the user gets it wrong, we’ll display the correct answer and return the value 0.

Recall that the input() function returns user input as a string? Hence, when you compare the user’s input with the correct answer (obtained in Exercise 4.4), you have to do some type casting to change the user input to an integer. When changing the user input to an integer, you should use a try, except statement to check if the user typed in a number. If the user typed in a string instead, the program should inform the user of the error and prompt the user to type in a number.

You can use a while True loop to keep prompting the user for a number as long as he/she fails to do so. Writing while True is equivalent to writing something like while 1==1. Since 1 is always equals to 1 (hence always True), the loop will run indefinitely.

Here’s a suggestion on how you can use a while True loop for this exercise.

while True:

try:

cast user’s answer to an integer and evaluate the answer

return user score based on the answer

except:

print error message if casting fails

prompt user to key in the answer again

The while True loop will keep looping since the while condition is always True. The loop will exit only when the try block executes correctly and reaches the return statement.

Try this exercise. Once you are done, we can proceed to Part 2 where we write the actual program.