do you guys help newbies??

James T. Dennis jadestar at idiom.com
Wed Nov 27 15:01:44 EST 2002


malik m <malik_martin at hotmail.com> wrote:
> hi yea i messed up here's the code sorry about the html too i was
> using it through hotmail while i learnd and still am learning how to
> use newsgroups....

> #chapter 3 test control structures 3.3
> print "hello, this program will tell me how many miles per gallon your
> car drives by the \n amount of miles entered by the user along with
> the size of his/her tank."

 Clearly this is From Deitel(TM)'s _Python:_How_To_Program_ 
 That's a book that spoonfeeds the prospective programmer.  It's not
 held in high regard among Pythonistas.  

 One of my complaints is that the text is pretty unclear (at least in 
 the early chapters) about the nature of Python's exception handling.  
 In several places the Dietel book refers to various conditions which 
 "cause a program to terminate with an error" (or words to that effect).  
 In most books on Python the authors make it clear that exception handling
 is a fundamental control structure in Python.  Thus it should have been
 covered in this chapter. Also all references to abnormal terminations
 from programming or data errors should refer to the specific exception
 that is being raised.

 (That critique is for the rest of the readership: Let me explain for
 you, Malik:  When you make a mistake in Python, such as divide by 
 zero or try to use a string as a number then Python will "raise an 
 exception."  By default exceptions are reported as an error, and 
 the program exits at that point.  When a Python program terminates
 due to an exception, it prints some debugging information called a 
 traceback.  Good books on Python programming explain this in the first
 or second chapter.  When ever you're trying to get help with a python
 script in a newsgroup or on a mailing list, you should post the plain
 text of a *small and relevant* fragment of your code and, if possible
 a capture of the traceback.   (Under UNIX a quick way to capture the
 traceback is to use a command like:  foo.py 2> ~/tmp/foo.err; that is
 redirect 'stderr' to a file).  It's also possible for your program to
 catch specific types of exceptions that you know might be caused by
 specific blocks of your code.  This is perfectly normal under python 
 and should NOT be avoided.  However it should be done carefully, so 
 you don't make your programming errors harder to find).

> #initialize variables
> totalMilesDriven = 0 #total miles driven

> totalGallonsPerTank = 0 #total gallons per tank

> counter = 0 #bogus counter

> #processing phase
> gallonsPerTank = raw_input("Enter how many gallons your tank holds, -1
> to end:")

> gallonsPerTank = int(gallonsPerTank)

 This should be something more like:

 try: 
	gallonsPerTank = int(gallonsPerTank)
 except ValueError:
	gallonsPerTank = -1
	# Take an invalid input as a call to exit.

 ... or (better yet):

 TankSize = "Enter how many gallons your tank holds, -1 to end:"
 while 1:
	gallonsPerTank = raw_input(TankSize)
	try: 
		gallonsPerTank = int(gallonsPerTank)
		break
	except ValueError:
		print "Invalid Response!  Please try again."

 (Actually this code is still a little weak because it will reject
 floating point numbers. But it will have to suffice for pedagogical
 purposes).

> milesDriven = raw_input("enter how many miles driven, -1 to end:")
> milesDriven = int(milesDriven)

 See above.  You have to validate your inputs. 

 Generally it's wise to isolate you input validation code into
 a function.  This allows your main program code to focus on 
 the main task at hand (hiding the details of validation from maintainers
 and readers of that code) and helps reduce the chance that you'll
 duplicate that code in multiple places in your program (as  you've
 done below, within the same prompts and raw_input functions inside
 your while loop).

 Actually it's even better to create a class (object template).  That
 class (or functions within it) would then be responsible for it's own 
 validation.   However, that is probably overkill for such a simple
 exercise.  The question becomes, "who" (which object/class) would be
 responsible for the actual inputs.  I don't like the idea of hiding
 the raw_input() calls inside of an object's initializer.  Of course
 raw_input() is not used much in real Python programming because usually
 some other interface is being implemented (CGI forms processing, GUI, 
 network, file, or database, for some examples).
 
 I'm not going to speculate on an OO design for this exercise.

> while gallonsPerTank or milesDriven != -1:
 
 This looks wrong.  I think you mean to say:

 while gallonsPerTank != -1 and milesDriven != -1:

 ... Because your conditional expression will be short-circuited
 any time gallonsPerTank set bound to *any* value other than a Python
 "false" (numeric zero, empty string, tuple, etc).  In any other case
 (gallonsPerTank is set to any non-zero or non-empty value) then
 the next expression will be evaluated and that result will be returned
 for the expression as a whole.

 So your loop won't exit until gallonsPerTank is 0 (or empty) and
 milesDriven is anything other than -1.

>    averagea = milesDriven / gallonsPerTank

 Most people want floating point (non-integer) results from averages.
 So:

 averagea = float(milesDriven) / gallonsPerTank

 ... will *coerce* (force) the whole expression to return a floating 
 point value.

 Normally I'd complain about your failure to check for gallonsPerTank
 being zero.  Ironically the erroneous while condition actually guards
 against that.  However, when you change the while condition to something
 that expresses your intent, then you could have a case where 
 gallonsPerTank == 0

 You can handle this one of three ways:
	1) use a conditional before the division:

 if gallonsPerTank: 
	 averagea = float(milesDriven) / gallonsPerTank
 else:
	### What?
	import sys
	averagea = sys.maxint
	### You got infinite mileage from "no gas"?

	2) use Python's exeption handling?

 try:
	 averagea = float(milesDriven) / gallonsPerTank
 except ZeroDivisionError: 
	import sys
	averagea = sys.maxint
	### See above about this absurdity.

 ... I think you'll see the problem with both of these approaches.
 You can't have a valid mpg calculation if you had NO gasoline.

 That leaves us with:

	3) treat 0 (zero) as an invalid value for gallonsPerTank.

 (Go back to your gPT input function and change it to something like:

 while 1:
	gallonsPerTank = raw_input(TankSize)
	try: 
		gallonsPerTank = int(gallonsPerTank)
		if gallonsPerTank == 0: 
			print "Can't get any mileage with no gasoline"
			print "... unless you're on a bicycle"
			print "... or in a rowboat ;)"
			continue  
		break
	except ValueError:
		print "Invalid Response!  Please try again."


>    counter = counter + 1
>    print "your miles per gallon are", averagea
>    totalMilesDriven = totalMilesDriven + milesDriven
>    totalGallonsPerTank = totalGallonsPerTank + gallonsPerTank

	Don't you mean just totalGallons or totalGallonsConsumed?

>    gallonsPerTank = raw_input("Enter how many gallons your tank
> holds, -1 to end:")
>    milesDriven = raw_input("enter how many miles driven, -1 to end:")

 See above for my comment on duplicating your input code and thus
 having multiple places where you'd need to maintain any validation
 code.  This is called "cut & paste" programming --- and is a BAD thing.

 Here's sample function for you to use:

 def GetGallonsPerTank(prompt="Enter tank capacity in gallons, -1 to exit"):
    while 1:
       response = raw_input(prompt)
       try:
          r = int(response)
          if r <= 0: 
             print "Are you on a bicycle?"
             continue
          break
       except ValueError: 
          print "Must enter an integer"
    return r		

 ... (untested). 

 If we create another input function for GetMilesDriven() it will
 be almost the same.  That's irritating (more code duplication).
 If the code were *exactly* the same we could use one function for
 both inputs.  If there's enough similarity (and a reasonable assurance
 that the validation requirements for the different variables won't 
 diverge too much in the future, we could encapsulate the differences
 into parameters or help functions and pass those to our common function
 as arguments.

 So we might have:

 def GetValidInput(prompt, validate, errormsg):
    while 1:
       response = raw_input(prompt)
       try:
          return validate(response)
       except ValueError: 
          print errormsg

 ... along with functions for ValidateTankCapacity() and ValidateMileage()
 which might look something like:

 def ValidateTankCapacity(proposed):
	r = int(proposed)
	if r <= 0: raise ValueError
	return r

 ... etc.

 You'd call this with statements like:

   inputPrompt = "Enter Gas Tank Capacity, -1 to exit: ",
   invalidMsg  = "Must enter a whole number!"
   tankCapacity = GetValidInput(prompt, ValidateTankCapacity, invalidMsg)
	
   inputPrompt = "Enter Mileage Driven, -1 to exit: ",
   mileageDriven = GetValidInput(prompt, ValidateTankCapacity, invalidMsg)

 Again, all of this is probably overkill for this exercise.
	

> #termination phase
> if counter != 0:
>    average = float( totalMilesDriven ) / float( totalGallonsPerTank )

 Only need to coerce one of these to a float.

>    print "The total miles per gallon is", average
> else:
>    print "No data entered"

 



More information about the Python-list mailing list