A Functioning Stand Alone Python Program

Interviewer:     The Magna Carta – was it a document signed at Runnymede in 1215 by King John pledging independence to the English barons, or was it a piece of chewing gum on a bedspread in Dorset? The latter idea is the brainchild of a man new to the field of historical research. Mr Badger, why – why are you on this programme?
[Pull back to show Mr Badger. He wears a flat cap and has a Scots accent.]
Badger:     Well, I think I can answer this question most successfully in mime. (mimes incomprehensibly)

In the last tutorial we created a file using our text editor and saved a function to it.  This file was called trivia.py and in it was the module “trivia”.  We then started Python in a console and import()ed the trivia module.  Once imported, it created a “namespace” and we could access the askQuestion() function from within the trivia namespace by using a dot – trivia.askQuestion().  In order for the module to work properly we had to include an import statement within the module itself so that everything that the module relied upon was imported within the module.  We then manually loaded our data from a pickle file we created and, manually, ran the askQuestion() function on the first question in our data store.   Finally we added docstrings to the function and the module.

In this tutorial we’re going to try to do much the same thing again, but without using the Python interpreter.  That is, we will need to take the things we did in the interpreter and implement them in our trivia.py file.  We will have a functioning (although still quite simple) stand alone Python program.

To start, open up trivia.py in your text editor.
Looking at the last tute, we can see that we used the cPickle module, so that will need to be imported.  We then opened the existing pickle file, and loaded the stored pickle.

So, first, edit the file to add import cPickle after import random:

import random
import cPickle

Next, let’s add a ‘constant'[1] to store our the filename of our pickle file.  Constants are not really all that constant in Python, in that Python will let us change their value later if we choose.  However, by convention, if you name a variable with all caps, then it is assigned a value only once (usually at the start of the program).  Add this after the imports but before the first definition:

QUESTIONS_FILENAME = 'p4kTriviaQuestions.txt'

Now, we add some code to load the questions into an array.  In theory, this code could go anywhere in the file after the variable QUESTIONS_FILENAME has been given a value.  However, it’s better if we put it at the end of the file after the function definition.  It should not be indented (remember that Python identifies code blocks by indentation):

fileObject = open(QUESTIONS_FILENAME,'r')  # note: 'r' for read
questionsList = cPickle.load(fileObject)  # load the questions
fileObject.close()   # finished reading, so close the file

If you go back through the previous tutes (eg the previous tute) you’ll see that this is the same code we used in the interpreter.   This shouldn’t be surprising because Python runs the code in this program as if it was being typed into the interpreter.[2]  Now that the questions have been loaded, let’s iterate through each of them, asking them in turn:

for question in questionsList:
    askQuestion(question)  
    # note, that because we're within the module we can just
    # use the function's name directly without being qualified by the
    # trivia namespace.  If you put trivia.askQuestion, the code would not work.

Save the file [see below for what the code should look like], then go to a console/command line – ie the thing from which you have run the Python interpreter in earlier tutes.  Do not run the interpreter itself. Rather, we run the file by typing the following and pressing the ‘enter’ or ‘return’ key:

> python trivia.py
Who expects the Spanish Inquisition?
0 .  Brian
1 .  Eric the Hallibut
2 .  An unladen swallow
3 .  Nobody
4 .  Me!
Enter the number of the correct answer: 3
Correct! Hooray!
What is the air-speed velocity of an unladen swallow?
0 .  23.6 m/s
1 .  10 m/s
2 .  14.4 m/s
3 .  What do you mean? African or European swallow?
Enter the number of the correct answer: 3
Correct! Hooray!
Is this the right room for an argument?
0 .  Down the hall, first on the left
1 .  No
2 .  I've told you once
3 .  Yes
Enter the number of the correct answer: 2
Correct! Hooray!

Note that this is from the command line – not the Python interpreter.  You can tell because there is only one > (you might have something different as your > thingamy) where the interpreter has three >>>

As you can see the two lines:

for question in questionsList:
    askQuestion(question)

Cause the program it iterate through each element in the list questionsList and, for each element, to call the askQuestion() function with that element as a parameter.  If you remember, each of these elements is, itself, a list.

Homework:  think about how we would need to change this program to keep track of the player’s score

Bonus points: actually change the program so that it keeps track of the player’s score and prints out the score after all questions have been asked.

Notes:

1.  Technically, in the code:

QUESTIONS_FILENAME = ‘p4kTriviaQuestions.txt’,

QUESTIONS_FILENAME is a variable, and ‘p4kTriviaQuestions.txt’ is the constant.  However, in the text I’m referring to the variable as if it was the constant.

2.  Actually, when you run a program from the command line Python reads the whole file and pre-compiles it first (or uses an existing pre-compiled version if you haven’t changed the file) then runs the pre-compiled version.  Have a look for file called trivia.pyc in your directory.

Source Code

The complete file should look like this:

'''
The place for a doctring which was an exercise for you to complete in the previous tute.
'''

import random
import cPickle

QUESTIONS_FILENAME = 'p4kTriviaQuestions.txt'

def askQuestion(questionList):
    '''
    Given a question in a form of a list, with the first entry being the question,
    the next entry being the correct answer to the question and one or more other
    entries, each of which is an incorrect answer, pose the question, randomising the
    answers and test whether the answer is correct
    '''
    question = questionList[0]
    answers = questionList[1:]
    numberOfAnswers = len(answers)-1
    # -1 because the first entry in the list is number 0
    correctAnswer = random.randint(0, numberOfAnswers)
    # choose an answer at random
    # then swap it with the correct answer
    spam = answers[correctAnswer]
    answers[correctAnswer] = answers[0]
    answers[0] = spam
    print question
    for i in range(len(answers)):
        print i,'. ',answers[i]
    answer = raw_input('Enter the number of the correct answer: ')
    if answer == str(correctAnswer):
        print 'Correct! Hooray!'
    else:
        print 'Wrong...'

fileObject = open(QUESTIONS_FILENAME,'r')  # note: 'r' for read
questionsList = cPickle.load(fileObject)  # load the questions
fileObject.close()   # finished reading, so close the file

for question in questionsList:
    askQuestion(question)  
    # note, that because we're within the module we can just
    # use the function's name directly without being qualified by the
    # trivia namespace.

Baby Steps with Our Text Editor

Second Interviewer     I didn’t really call you Eddie-baby, did I, sweetie?
Ross     Don’t call me sweetie!!
Second Interviewer     Can I call you sugar plum?

In this tutorial we’re going to start using the power of the text editor that I’ve been badgering you about over the last few tutorials.  We are going to go back to the earlier tutorials and resurrect some of the code there.

Exercise:
Create a new document with your text editor
Save the document to a file called trivia.py  You need to be careful that your text editor doesn’t add ‘.txt’ to the end of the file’s name as some have the habit of doing.
copy the following function into the file (originally found here, with some comments dropped):

def askQuestion(questionList):
    question = questionList[0]
    answers = questionList[1:]
    numberOfAnswers = len(answers)-1
    # -1 because the first entry in the list is number 0
    correctAnswer = random.randint(0, numberOfAnswers)
    # choose an answer at random
    # then swap it with the correct answer
    spam = answers[correctAnswer]
    answers[correctAnswer] = answers[0]
    answers[0] = spam
    print question
    for i in range(len(answers)):
        print i,'. ',answers[i]
    answer = raw_input('Enter the number of the correct answer: ')
    if answer == str(correctAnswer):
        print 'Correct! Hooray!'
    else:
        print 'Wrong...'

save the changes you’ve just made to the file
It should look something like this (notice the code highlighting in Kate.  A basic text editor like NotePad won’t do this highlighting for you):

now, start a python prompt from the command line and import the module: (note: for this to work you must be in the same directory as the trivia.py file when you start the Python prompt Google “cd change directory” if you have problems)

>>> import trivia
>>> dir(trivia)
['__builtins__', '__doc__', '__file__', '__name__', 'askQuestion']

Can you see that we’ve imported our trivia file at the Python prompt (note: not import trivia.py, Python automatically adds the .py when it’s looking for the module).  We’ve used introspection to see its attributes and methods – you can see the askQuestion() function there.  We can’t actually ask a question at the moment, because we don’t have a question to ask, so let’s recover those we were storing that pickle file from earlier tutes:

>>> import cPickle
>>> filename = 'p4kTriviaQuestions.txt'
>>> fileObject = open(filename,'r')
>>> triviaQuestions = cPickle.load(fileObject)
>>> fileObject.close()
>>> triviaQuestions
[['Who expects the Spanish Inquisition?', 'Nobody', 'Eric the Hallibut', 'An unladen swallow', 'Brian', 'Me!'], ['What is the air-speed velocity of an unladen swallow?', 'What do you mean? African or European swallow?', '10 m/s', '14.4 m/s', '23.6 m/s'], ['Is this the right room for an argument?', "I've told you once", 'No', 'Down the hall, first on the left', 'Yes']]
>>> triviaQuestions[0]
['Who expects the Spanish Inquisition?', 'Nobody', 'Eric the Hallibut', 'An unladen swallow', 'Brian', 'Me!']

Now, let’s get a single question, because that’s what the askQuestion() function is expecting:

>>> question = triviaQuestions[0]

So, in theory we should be ready to run this through our askQuestion() function.  Let’s try it:

>>> trivia.askQuestion(question)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "trivia.py", line 6, in askQuestion
    correctAnswer = random.randint(0, numberOfAnswers)
NameError: global name 'random' is not defined

Uh-oh, it didn’t work because we haven’t imported the random module. Gosh darn it.  Let’s add that at the start of our file so the first couple of lines now look like this:

import random

def askQuestion(questionList):
    question = questionList[0]
    answers = questionList[1:]

Now we go back to the Python command line and import it again:

>>> import trivia
>>> trivia.askQuestion(question)

Unfortunately, this also fails exactly as it did before – even though we know we’ve imported the random module.  The reason is that import only happens once.  If you change a module, then import it a second time, it doesn’t do anything.  However, we can update a module by using the reload() function (you can also exit the Python prompt, restart Python and then import, but reload() is easier).  Once we do that, things start working as we expect them to:

>>> reload(trivia)
<module 'trivia' from 'trivia.py'>
>>> trivia.askQuestion(question)
Who expects the Spanish Inquisition?
0 .  An unladen swallow
1 .  Eric the Hallibut
2 .  Nobody
3 .  Brian
4 .  Me!
Enter the number of the correct answer: 2
Correct! Hooray!

So, some things to notice:
* when we imported our module, we imported the name of the file less the ‘.py’
* on import it created a ‘namespace’ called trivia.  The function we defined was in the trivia namespace and we accessed it using a dot: trivia.askQuestion().  Had we just typed in askQuestion() the computer would not know what we are talking about:

>>> askQuestion(question)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'askQuestion' is not defined

We can import functions individually using a different syntax and, if we do, the function’s name is present in the base namespace and it can be called directly:

>>> from trivia import askQuestion
>>> askQuestion(question)

However, doing this is frowned upon because it ‘pollutes’ the namespace.  In particular, you may end up with collisions between functions imported into the namespace.  So if you have two modules, each with the a function of the same name, but of different effect the interpreter would have a problem if you from imported both of them.  Try to avoid using the import from syntax at least until you’re familiar with why it’s a problem.

Something our function is missing is a doc string.  Let’s add one.  Go back to your text editor and edit the function so now it reads:

def askQuestion(questionList):
    '''
    Given a question in a form of a list, with the first entry being the question,
    the next entry being the correct answer to the question and one or more other
    entries, each of which is an incorrect answer, pose the question, randomising the
    answers and test whether the answer is correct
    '''
    question = questionList[0]
    answers = questionList[1:]

Save the file
Go to the Python prompt and reload() the module, then look at the docstring:

>>> reload(trivia)
<module 'trivia' from 'trivia.py'>
>>> print trivia.askQuestion.__doc__

        Given a question in a form of a list, with the first entry being the question,
        the next entry being the correct answer to the question and one or more other
        entries, each of which is an incorrect answer, pose the question, randomising the
        answers and test whether the answer is correct

Now, the important thing to note is that we were able to change part of the function without retyping all of it.  Going forward we will be able to extend this module by adding bits to it.

Homework:
Create a docstring for the trivia module itself, describing what the trivia module will be doing (make a list of the things you think it should eventually do in order to make a trivia game).  Work out where the docstring needs to go in trivia.py.  Save the file, then reload the module from the Python prompt and print the docstring out.

Homework:
Think about what part of the Python language we might use to ask all of the questions stored in the pickle file, rather than just the first one.

Catching Our Breath

Character     Servant ho!
He then goes underwater again. The servant in the boat steps into the water and goes under. Cut to announcer, now up to his waist in sea.

In this tutorial we review what we have covered over the last few tutorials, and lay some more groundwork for working with files and using the text editor that you organised in the last tutorial.

In recent tutorials we’ve learned quite a bit:

  • pickling basics: we learnt about a way of storing data called “pickling”. We did some basic pickling. Pickles are a way of storing data. This means that, in our trivia quiz, we’ll be able to add questions from time to time, without having to retype all of the questions we have added earlier.
  • pickling data: we took our understanding of pickles and applied it to our data. We saw how we can save the data that we were using for our game into a file for later use.
  • introspection: Python objects are able to tell you about themselves. In particular we learnt about the .__doc__ string and the dir() functions. Introspection allows us to find out stuff about the program we are working with. In some cases it can be a sort of help file by telling us some stuff about an object. Introspection is part of Python ‘for free’. It is available whenever you run Python without having to (eg) import introspection in the way we imported cPickle.
  • more on pickles: how to load data that’s been stored in a file, modify it and dump it back to the file. So we can keep data that our programs rely on in a file. The point of this tutorial was to show how the pickle can be used like a locker for your data. If you put your data into a pickle not only will it be there for you to take out later, but you can take it out, change it, then replace the old data with the new data.
  • dictionaries. Dictionaries are a little like lists, except you can map a given value to an index. In the homework, your example was to map names of your friends to their favourite game). Unlike lists, which access values by knowing their position in the list, dictionaries access values by giving the value a name, called a ‘key’. In order to use a list, you are dependent upon knowing the order of the items in the list. This can cause problems if you want to change the sort of data stored in the list. In this tutorial we saw that we couldn’t add an alternative correct answer because there was literally nowhere in our list to store it (because we’d earlier decided to consider all entries in the list after a certain point to be incorrect answers. Adding an alternative correct answer would invalidate all the existing data (eg every question that you had already written to the pickle file). Because dictionaries access values through keys they do not suffer from this problem. Position is not important for dictionaries. If you have data stored in a dictionary, then to add new categories of answer is a simple question of adding a new key to identify the new category of data; and, finally,
  • in the most recent tutorial, I told you to go get a text editor and learn the basics of how to use it. Using a text editor will allow us to store the Python code that we are writing in much the same way as we have been saving data in the other tutorials. One of the benefits of this is that we don’t need to retype code all the time. If we want to modify a function we just open the text editor and modify it – without having to retype it from scratch.

Most of the things we’ve covered here have to do with files and (therefore) the file system on your computer. In order to do further tutorials we’re going to be changing the file system, and if we do that we’ve got a problem in that we may end up deleting or changing something we didn’t want to. For that reason I’d like you to talk to a responsible adult and set up a separate folder on your hard drive specifically for these tutorials. Call it python4kids (or p4k if you are a lazy typist).

Homework: have a responsible adult set up a separate folder (also called a directory) for these tutorials.  Name it python4kids or p4k.

Homework: work out (ask someone or Google) how to change directory into this new folder so that any files you make are made here. Whenever you run Python change directory to this folder first, so that Python runs in the directory.

Homework: work out how to load files from this directory using your text editor and how to save files to this directory using your text editor.

Homework: Copy the pickle file we’ve been working with to this new directory. If you have not made any or many additions to it, you can just recreate it by running the tutorials in the directory.

If you do these things and whenever you do these tutorials you do them in that folder, saving files to that folder, then I don’t have to worry about accidentally overwriting your other data, because the only data that will be there is data we’ve created in these tutorials.

In terms of Python language features – so far we have covered quite a lot of them. We know about:

  • data: strings, numbers (integers), lists and dictionaries
  • controlling program flow: if/then/else, while, for
  • some regularly used in built functions: range, len
  • subroutines: def (functions), modules, import
  • other language features: objects, introspection

There is a big one which is still missing – classes.  We will get to classes in a few more… classes. Till then… do your homework.

Follow

Get every new post delivered to your Inbox.

Join 75 other followers