Danny Yoo dyoo at hkn.eecs.berkeley.edu
Sat Jul 24 00:26:36 CEST 2004

```
Hi Dick,

I took a closer look at your program; there's actually a significant thing
I can see that should shorten the program a bit.  It has to do with the
way that the code interacts with the user; in many places in your code,
your program asks the user for input, and uses a fairly regular way of
doing this.

Let's look at a few places where this happens.  I'll look at three blocks:

Block 1:
---
> while True:
>      print "If no decimal entered, a random decimal " \
>      "between 0.1 and 1.0 will be chosen."
>      # for exiting via ^C or ^D
>      try:
>          string = raw_input("Decimal: ")
>      except (TypeError, EOFError):
>           break
>      if string in ["x", "q"]:
>          break

Block 2:
---
>      while True:
>          choice = raw_input("Minimum error (e) or maximum denominator (d)? ")
>          if choice in ["x", "q"]:
>              break
>          elif not (choice in ["e", "d"]):
>              print "Enter d or e"
>              continue
>          else:
>              break
>      if choice in ["x", "q"]:
>              break

Block 3:
---
>          while True:
>              print "If no maximum denominator entered, the default is 100"
>              maximumDenom = raw_input("Maximum denominator: ")
>              if maximumDenom in ["x", "q"]:
>                  break
>              elif maximumDenom == "":
>                  maximumDenom = defaultMaximumDenom
>                  print "Maximum denominator is %g by default" % maximumDenom
>              else:
>                  try:
>                      maximumDenom = int(maximumDenom)
>                  except:
>                      print "That's not an integer! Try again."
>                      continue
>              break
>          if maximumDenom in ["x", "q"]:
>              break

Each of these blocks, conceptually, does something like this:

###
To get user input:
query the user from input
if we get 'x' or 'q':
let's quit the program
if no answer comes at us:
use a default value
show an error message and ask again
otherwise, use that user input as our final answer
###

The following is code that implements the pseudocode above:

###
def getUserInput(queryPrompt,
isGoodInput,
defaultInput):
"""Queries the user for an input.  Takes in four parameters:

queryPrompt: the prompt we pass to raw_input.

isGoodInput: some boolean function that tells us if the input looks
good to us.

defaultInput: the default value we return if the user just presses
enter.

If no defaultInput is defined, we keep asking.  If the input is 'x' or
'q', we raise a SystemExit to quit the program.
"""
while True:
userInput = raw_input(queryPrompt)
if userInput in ['x', 'q']:
raise SystemExit
elif userInput == "" and defaultInput:
return defaultInput
elif isGoodInput(userInput):
return userInput
else:
###

It's a little large, but most of it is commentary.

The value of writing a general function like this is that the blocks above
can now use getUserInput() to do the brunt of the work of handling user
input in a nice way.

Here's a quick example to show how it might work:

###
...                       lambda x: x == 'secret',
...                       None)
>>>
>>>
>>> print passwd
secret
###

You can ignore the 'lambda' part for the moment; we can get get back to it
in a moment.  But you can see that it does a lot, for just a single call
to getUserInput().  And that's powerful.

For example, Block 3, which looked like:

>          while True:
>              print "If no maximum denominator entered, the default is 100"
>              maximumDenom = raw_input("Maximum denominator: ")
>              if maximumDenom in ["x", "q"]:
>                  break
>              elif maximumDenom == "":
>                  maximumDenom = defaultMaximumDenom
>                  print "Maximum denominator is %g by default" % maximumDenom
>              else:
>                  try:
>                      maximumDenom = int(maximumDenom)
>                  except:
>                      print "That's not an integer! Try again."
>                      continue
>              break
>          if maximumDenom in ["x", "q"]:
>              break

can be reduced a single call to getUserInput() and a definition of a
function that tells us if we're looking at an integer.

###
def looksLikeInt(value):
"""Returns True if the value looks like an integer, and otherwise
returns False."""
try:
int(value)
return True
except ValueError:
return False

maximumDenom = int(getUserInput("Maximum denominator: ",
looksLikeInt,
"That's not an integer!  Try again."
defaultMaximumDenom))
###

Using getUserInput() is a little weirder than using a straightforward
raw_input(), but it does have versatility.  The key part of this is the
following: we have to pass it some notion of what a good answer looks
like, so that it knows when to keep asking.

In the example above, we wrote a quick-and-dirty 'looksLikeInt()'
function, and then passed that off to getUserInput(), so that
getUserInput() can know what we think a satisfactory answer looks like.

In Block 2, we can do something similar:

###
def isErrorOrDenominatorChoice(value):
return value in ['e', 'd']

choice = getUserInput("Minimum error (e) or maximum denominator (d)? ",
isErrorOrDenominatorChoice,
"Enter d or e",
None)
###

And again, we write a quick-and-dirty function to tell the system that 'e'
or 'd' is a good value to accept, and pass it off to getUserInput().

Does this make sense so far?

```