error handling in user input: is this natural or just laborious

James Stroud jstroud at mbi.ucla.edu
Fri Oct 6 23:15:49 EDT 2006


sam wrote:
> this does what i want, though i don't like the inner while loop having
> to be there
> 
> 
> def get_pct():
> 	while True:
> 		pct_list=[['cash', 0], ['bond', 0], ['blue', 0], ['tech', 0], ['dev',
> 0]]
> 		total=0
> 		for i in range(len(pct_list)):
> 			while True:
> 				pct_list[i][1]=raw_input('Please enter the percentage value for %s:
> ' %pct_list[i][0])
> 				if pct_list[i][1].isdigit() == False:
> 					print "You've messed up, do it again..."
> 				else:
> 					total+=int(pct_list[i][1])
> 					break
> 		if total == 100:
> 			return pct_list
> 			break
> 		else:
> 			print "You've messed up, do it again..."
> 
> 
> gosh, a lot of work to get some input. i must be missing something,
> though this is a lot better than what i had before...
> 

I think youwill like your code better if you take nested loops and put 
them in functions

In this following block, I have made changes to your revised code to 
show better technique on the small scale (removing tabs so it would 
format correctly without wrapping and breaking lines):

def get_pct():
   while True:

     # for things like your pct_list, begin with dictionaries in mind
     # the pairs you had were unnecessary clutter
     pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']


     # clean up code by moving long message strings outside of loops
     # this will help you see the logic of your code later
     msg = 'Please enter the percentage value for %s: '

     # Hmm--running totals, big PITA
     total=0

     # here is the dictionary we will be constructing
     pct_dict = {}

     # loop over lists and not over list indices
     # usually watch when you want to write "range(len(some_list))"
     for a_pct in pct_list:

        # 3rd level nested loop: think function
        while True:

            # see the python style guide for where to put spaces
            # around such things as equals signs
            pct_dict[a_pct] = raw_input(msg % a_pct)

            # doesn't seem to hit at the heart of what you want, but
            # I'll keep it for pedagogy (I would try to coerce to float
            # and catch
            # also, use not instead of "== False"
            # e.g: if not pct_list[a_pct].isdigit()
            # better yet, put the positive case in the "if" statement
            try:

                # maybe even better would be to total later so you
                # don't have to keep track of a running total
                # also, coercing to int here anyway, so just make it
                # the test with try:except
                total += int(pct_list[a_pct])
                break

            except ValueError, e:
                print "You've messed up, do it again..."

         # the pct_list may add to 100 as floats, but int rounding
         # might kill the total and make the test fail
         # perhaps add as floats and test for a small range around
         # 100 that allows for float arithmetic error
         if total == 100:
             return pct_list
         else:
             print "You've messed up, do it again..."


That's as few changes I could make to it. Let's see it without the comments:


def get_pct():
   while True:
     pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
     msg = 'Please enter the percentage value for %s: '
     total=0
     pct_dict = {}
     for a_pct in pct_list:
        while True:
            pct_dict[a_pct] = raw_input(msg % a_pct)
            try:
                total += int(pct_list[a_pct])
                break
            except ValueError, e:
                print "You've messed up, do it again..."
         if total == 100:
             return pct_dict
         else:
             print "You've messed up, do it again..."


A little cleaner. Now, lets tighten it up a bit more, and put nested 
loops into functions. Im getting rid of keeping track of the running 
total, that will clean a lot. I'm also going to be a little more 
mathematically saavy.

def get_pct():
   while True:
     pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
     pct_dict = get_pct_dict(pct_list)
     total = sum(pct_dict.values())
     if (total < 99.999) or (total > 100.001):
       print "You've messed up, do it again"
     else:
       return pct_dict()

def get_pct_dict(pct_list):
   pct_dict = {}
   for a_pct in pct_list:
     pct_dict[a_pct] = get_one_pct_value(a_pct)
   return pct_dict()

def get_one_pct_value(a_pct):
   msg = 'Please enter the percentage value for %s: '
   while True:
     try:
        return float(msg % a_pct)
     except ValueError:
        print "You messed up, try again."

Now, all testing is done at the point where it is needed. There are no 
running totals that could cause accounting errors, your final data 
structure is an easy to use dict, unecessary tests have been eliminated, 
loops have been de-nested visually and logically, and, most importantly, 
the indentation level is kept manageable.

I think you will be able to see how this latter code evolved from yours. 
  I used to program just like you and it has taken me a few years to 
develop these little rules for tightening my code.

James

-- 
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/



More information about the Python-list mailing list