I would suggest using a dictionary for the grades, for two reasons:
(1) It'll make your life a lot easier (see code below)
(2) Your code currently contains a bug that will allow a silly or malicious user to get weird results or break the code.
if grade_str not in 'A,A-,A+,B,B-,B+,C,C-,C+,D,D-,D+,F':
print "Error, use A,A-,A+,B,B-,B+,C,C-,C+,D,D-,D+,F"
Note that if the user enters 'A,A-' or '+' or ',' or something similar, the test will pass even though those aren't valid grades! Use of the dictionary will fix that issue.
Generally, when you have constants like "A", "A-", etc... in your program, the philosophy is to only store the constants once. The reason is that if you later decide to change your constants, you won't have to go hunting through your code to find all the instances of that constant. Instead, you can change them once and be done.
Here's one way to implement that principle:
import string
import math
class Student:
valid_grades = {"A+":4.0, "A":4.0, "A-":4.0, "B+":3.0, "B":3.0, "B-":3.0, \
"C+":2.0, "C":2.0, "C-":2.0, "D+":1.0, "D":1.0 ,"D-":1.0, \
"F":0.0}
valid_symbols = valid_grades.keys()
valid_symbols.sort()
def __init__(self, name, hours, qpoints):
self.name = name
self.hours = float(hours)
self.qpoints = float(qpoints)
def getName(self):
return self.name
def getHours(self):
return self.hours
def getQpoints(self):
return self.qpoints
def gpa(self):
return self.qpoints/self.hours
def addLetterGrade(self, letter, credits):
self.hours = credits
if letter in Student.valid_grades:
ngrade = Student.valid_grades[letter]
self.qpoints = credits * ngrade
print self.qpoints, self.hours
else:
print "Error! invalid grade!"
def main():
print
print "This program extends the modified student class program by"
print "implementing an add letter grade method"
print
stu = Student("stu", 0.0, 0.0)
done = False
while done == False:
while True:
grade_str = raw_input("Enter grade (" + ','.join(Student.valid_symbols)+\
" or just Enter=exit loop): ").upper()
print
if grade_str == "":
done = True
break
if grade_str not in Student.valid_grades:
print "Error, use "+','.join(Student.valid_symbols)
else:
break
while done == False:
credits_str = raw_input("Enter credits (number of hours): ")
try:
credits = float(credits_str)
break
except ValueError:
print "Error, use floating point number"
if done == False:
stu.addLetterGrade(grade_str, credits)
if stu.getHours() == 0.0:
print "Zero credit hours recorded"
else:
print "Final GPA = ", stu.gpa()
main()
The dictionary is now the only place that the grade symbols get defined. If later you wish to change B+ to be 3.33 or 3.25, you can do that with ease and confidence that your code is consistent. Or, if the school decides to allow teachers to award "F+"s, you can add it to the dictionary without having to double-check all your print statements.
And, the endless chain of 'elif's has gone away! :)
You still have one bug that needs attention: it is currently possible for the user to assign negative credit hours to a course.
Hope it helps,
Jeff