[Tutor] Re: Your advice, please [raw_input(), higher order functions]

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
    else if a bad answer comes at us:
        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,
                 badAnswerMessage,
                 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.

    badAnswerMessage: the message we print out if the input looks bad.

    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:
            print badAnswerMessage
###


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:

###
>>> passwd = getUserInput("Password Please! ",
...                       lambda x: x == 'secret',
...                       'Bad Password!',
...                       None)
Password Please! abracadabra
Bad Password!
Password Please! please
Bad Password!
Password Please! secret
>>>
>>>
>>> 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?



More information about the Tutor mailing list