[Tutor] help on newbie program

Tom Plunket py-tutor@fancy.org
Sat May 31 14:04:01 2003


Nick Jensen wrote:

> I understand parts of it, but not all of it as a whole.

Ok- I'll walk through it with you.  :)

I'll jam through it as the parser is going to see it.

> ## This program runs a test of knowledge

comment.  :)

> true = 1
> false = 0

this defines two global variables.

> # First get the test questions
> # Later this will be modified to use file io.
> def get_questions():
>     # notice how the data is stored as a list of lists
>     return [["What color is the daytime sky on a clear day?","blue"],\
>             ["What is the answer to life, the universe and everything?","42"],\
>             ["What is a three letter word for mouse trap?","cat"]]

This defines a function called "get_questions" that takes no
arguments.

> # This will test a single question
> # it takes a single question in
> # it returns true if the user typed the correct answer, otherwise false
> def check_question(question_and_answer):
>     #extract the question and the answer from the list
>     question = question_and_answer[0]
>     answer = question_and_answer[1]
>     # give the question to the user
>     given_answer = raw_input(question)
>     # compare the user's answer to the testers answer
>     if answer == given_answer:
>         print "Correct"
>         return true
>     else:
>         print "Incorrect, correct was:",answer
>         return false

This defines a function called "check_question" which takes one
argument.  Looking at the way the argument is used, it needs to
be something that is subscriptable with two entries, either a
two-element list, or a two-element tuple.  Probably other things
could work too, but I'm a Python newbie (but have been a
professional programmer for 7+ years) myself.

> # This will run through all the questions
> def run_test(questions):
>     if len(questions) == 0:
>         print "No questions were given."
>         # the return exits the function
>         return
>     index = 0
>     right = 0
>     while index < len(questions):
>         #Check the question
>         if check_question(questions[index]):
>             right = right + 1
>         #go to the next question
>         index = index + 1
>     #notice the order of the computation, first multiply, then divide
>     print "You got ",right*100/len(questions),"% right out of",len(questions)

This defines a function called "run_test" which takes one
argument.  Again, looking at how the argument is used, it needs
to be subscriptable, but in this case it can have any length
(notice the use of 'len(questions)' makes the function loop as
many times as there are questions).  Note that this loop is not
really "the Python way," though, but I'll discuss that later.

> #now lets run the questions
> run_test(get_questions())

Finally we get a line that asks the interpreter to actually do
something with all of the functions that I've defined.  What this
does is call the function "run_test" with the result of the
function "get_questions".  Due to this dependency,
"get_questions" certainly must execute first.  Then, whatever
comes back from that as the return value gets passed as the
argument to "run_test".

When "run_test" finishes, program flow returns to the next line
in the script, and since there are no more lines to execute, the
program terminates.

Now- how does the code actually work?  Well- "get_questions" is
called.  That function returns a list of lists, in this case, the
list is three elements long, and the lists within it are each two
strings.  So this function returns this list of lists, and that
return value then goes to...

"run_test" is called with the return value from "get_questions",
so that list-of-lists is passed in.  If the length of that list
is zero, an informative message is printed, and program flow
returns to the point where "run_test" was called.  If there are
questions to ask, two variables are created.  One is "index",
which is used to index into the list of "questions", and the
other is "right", which is used to count the number of correct
answers.  Each of these variables are initialized to zero
(remember, in the case of "index", lists and so forth's first
elements are index zero).  Now, we enter a loop- while index is
less than the number of questions, we have an if statement that
checks the return value of "check_question".  So, Python now
calls "check_question" with the "index"'th entry in the
"questions" list-of-lists.

Jumping to "check_question", we see that the argument that we get
is one of those sub-lists in the list-of-lists returned by
"get_questions".  So, the "question" is the first entry and
"answer" is the second; the first time into this function,
"question" is "What color is the daytime sky on a clear day?" and
"answer" is "blue".  Python then prints the "question" to the
console, and awaits input from the user.  The users types an
answer, and Python sticks that answer into the variable
"given_answer".  If "given_answer" is equivalent to the actual
"answer", the user is rewarded with the text "Correct", and the
function returns 1, which indicates success.  If the
"given_answer" does not match the correct "answer", the user is
told that the answer is not correct, and the function returns
zero to indicate failure.  This use of zero and one in this way
is historical to some extent, but basically boils down to zero
meaning "off" and one meaning "on."  We'll see how that works
when program flow returns...

To "run_test", we're now back in the if statement where we left
for "check_question".  Since the return value to "check_question"
isn't explicitly compared to any value, 'if' just checks to see
if the value is "on" or "off".  In the case where the return
value is "on" (i.e. the user answered the question correctly),
then the value of the "right" local variable is incremented.  If
the return value is "off", then nothing happens.  If you wanted
to, though, you could put in:

   else:
       wrong = wrong + 1

after that 'right = right + 1' and then be counting up the number
of wrong answers also, but that is a decision that you can make
for yourself.  :)

The next thing that happens, regardless of the user's answer to
the question, is that index is incremented.  The while loop then
continues- if index is still less than the length of the
"questions" list, do the loop again.

Once "index" is equal to or greater than the length of the
"questions" list (remember that for a list of 4 elements, and
since lists' first element is index zero, that the list's last
element is index 3.  Four elements are indexed 0, 1, 2, 3), the
while loop terminates and program flow continues to the print
call.  That last line of print just prints the user's results to
the console, and then program flow returns to the place where
"run_tests" was called.  (Since there is no explicit 'return'
statement, Python implicitly adds one after the last line of the
function.  Additionally, 'return' by itself with no arguments is
equivalent to 'return None', so the return value isn't really
intended to be checked.)

As mentioned above, once program flow returns from "run_tests",
there are no more lines in the program, so the script terminates.

Notes:
1) I mentioned that this code isn't exactly "the Python way"
   above:

      index = 0
      right = 0
      while index < len(questions):
          #Check the question
          if check_question(questions[index]):
              right = right + 1
          #go to the next question
          index = index + 1

   What I mean by that is that "the Python way" would probably be
   more along the lines of:

      right = 0
      for question in questions:
          #Check the question
          if check_question(question):
              right = right + 1

   As you can see, far fewer lines in this code.  Basically, the
   'for' loop just peels off the next element of the "questions"
   list each time through the loop, assigning that element to the
   "question" variable.

2) "check_question" isn't really a good name for that function,
   because it actually doesn't check the question at all.  The
   function *asks* the question and then checks the *answer*. 
   While some may argue that this is a decent "abbreviation", let
   me just point out that when you read code that you've written
   like this a year down the road, or when others read your code
   for the first time (like newbies reading a tutorial <g>), the
   reader starts to wonder why descriptive names weren't used in
   the first place.

That's it.  :)  Any questions?
-tom!