6
\$\begingroup\$

I'm a student learning Python, I made this calculator that will tell you the tuition for the next 5 years at an increase of 3% per year. How can I improve on this and did I do anything that isn't best practice? To revise my new version, Python tuition calculator 2.0

inState = 10000
outState = 24000
profGrad = 40000

years = []
tuitionIncrease = []


i = raw_input('Please input your type of residency, I for in-state, O for out-of-state, and G for graduate: ')


if i == 'I':
    costOfTuition = inState
elif i == 'O':
    costOfTuition = outState
elif i == 'G':
    costOfTuition = profGrad

x = 0
while x != 5:
    intMath = costOfTuition * 0.03
    tuitionIncrease.append(intMath)
    fnlMath = intMath + costOfTuition
    years.append(fnlMath)
    costOfTuition = fnlMath
    x += 1

academicYear1 = "${:,.2f}".format(years[0])
academicYear2 = "${:,.2f}".format(years[1])
academicYear3 = "${:,.2f}".format(years[2])
academicYear4 = "${:,.2f}".format(years[3])
academicYear5 = "${:,.2f}".format(years[4])

academicYear1Inc1 = "${:,.2f}".format(tuitionIncrease[0])
academicYear1Inc2 = "${:,.2f}".format(tuitionIncrease[1])
academicYear1Inc3 = "${:,.2f}".format(tuitionIncrease[2])
academicYear1Inc4 = "${:,.2f}".format(tuitionIncrease[3])
academicYear1Inc5 = "${:,.2f}".format(tuitionIncrease[4])

totalTuitionIncreaseSum = sum(tuitionIncrease)
totalTuitionIncrease = "${:,.2f}".format(totalTuitionIncreaseSum)
4
print('UNDERGRADUATE TUITION FOR THE NEXT FIVE YEARS ')
print('ACADEMIC YEAR          TUITION       INCREASE ')
print('-------------     ------------       -------- ')
print('2016-17             ' + academicYear1 + '        ' + academicYear1Inc1) 
print('2017-18             ' + academicYear2 + '        ' + academicYear1Inc2) 
print('2018-19             ' + academicYear3 + '        ' + academicYear1Inc3)
print('2019-20             ' + academicYear4 + '        ' + academicYear1Inc4)
print('2020-21             ' + academicYear5 + '        ' + academicYear1Inc5)
print('TOTAL TUITION INCREASE              '     + totalTuitionIncrease)
\$\endgroup\$
2
\$\begingroup\$

Your naming is a bit unPythonic. Most variable and function names should be snake_case, so tuitionIncrease should be tuition_increase. But constants should be UPPER_SNAKE_CASE, meaning inState is IN_STATE. You should also use clear names even if they're briefly used. i for input isn't great, as i is often used in for loops. Instead, you could use something like residency, that matches what the value means.

Speaking of those constants, you should actually put the three of them into a dictionary instead. A dictionary takes a string as a key and returns and associated value. So you can cut out the middle man and pass the user's input into the dictionary to get the number you need.

RESIDENCY_COST = {
                    "I": 10000,
                    "O": 24000,
                    "G": 40000,
                 }

About that input. You don't handle cases where the user enters input wrong, or if they enter a valid letter but as lowercase. Luckily we can do both of these easily. For avoiding the lowercase problem, use the str.upper() method. It turns all charactes in a string into uppercase so even if someone enters 'i' you'll change it to 'I'.

The way you can handle invalid input is by trying to get the value from the dictionary from above. You access values in a dictionary with the syntax dictionary[key] and you can write code so that you attempt to pass a key and if the key doesn't exist Python handles it. Here's how it would look:

residency = raw_input('Please input your type of residency, I for in-state, O for out-of-state, and G for graduate: ')
try:
    cost_of_tuition = RESIDENCY_COST[residency.upper()]
except KeyError:
    print ('Please enter I, G or O ONLY.')

But that's no good, that means we tell the user what to do and they don't get a second attempt. That's why you can wrap the whole thing in an infinite while loop and break it when you get valid input.

while True:
    residency = raw_input('Please input your type of residency, I for in-state, O for out-of-state, and G for graduate: ')
    try:
        cost_of_tuition = RESIDENCY_COST[residency]
        break
    except KeyError:
        print ('Please enter I, G or O ONLY.')

This will keep running the code in the while block forever until it reaches the break command. It can only reach there after a correct key is entered and cost_of_tuition has been set.

Instead of using this while x != 5, you can use a simple for loop.

for x in range(5):

Though since you don't need x, it's Python style to instead say for _ in range(5) to show that _ isn't a value you're actually using.

This block of calculation is a little unclear. What does intMath and fnlMath mean? Clearer names about the purpose and context of the values makes code infinitely more readable.

    intMath = costOfTuition * 0.03
    tuitionIncrease.append(intMath)
    fnlMath = intMath + costOfTuition
    years.append(fnlMath)
    costOfTuition = fnlMath

Also, have a loop where you append to lists, but then you manually read both those lists to create sets of 5 very similar values that should be lists. Why not make these directly in your loop? I'd change the loop block to this:

    intMath = cost_of_tuition * 0.03
    tuition_increase.append("${:,.2f}".format(intMath))
    fnlMath = intMath + costOfTuition
    academic_years.append("${:,.2f}".format(fnlMath))
    cost_of_tuition = fnlMath

Of course you need the sum too, but instead of using sum, just create the sum in the for loop you're already doing.

    total_tuition_increase += intMath
    tuition_increase.append("${:,.2f}".format(intMath))

You still need to format it, but you can do that within the print command:

print('TOTAL TUITION INCREASE              ${:,.2f}'.format(total_tuition_increase))

You seem to have misunderstood format. It's not intended primarily to be a variable converter, it's actually mainly for inserting values into strings, as I showed above. It has other handy features too, like specifying column widths so that spaces match up. So you could print one of your results like this instead:

print('2016-17 {:>10} {:>10}'.format(academic_years[0], tuition_increase[0]))

The 10 tells Python to use 10 spaces regardless of how long the string is, and the > makes it right justified. This will make it neater to read. I also think that since you have actual lists now, you could loop over them instead. You can use zip to attach 2 lists together and that way iterate over both at once. You will also need to loop over the year to achieve this (otherwise you'd print the same year every time). You can do that using a BASE_YEAR constant and using enumerate to see what iteration you're on.

Here's how the loop would look:

BASE_YEAR = 16
for i, (tuition, increase) in enumerate(zip(academic_years, tuition_increase)):
    print('20{}-{} {:>10} {:>10}'.format(i + BASE_YEAR, i + 1 + BASE_YEAR, tuition, increase)) 
\$\endgroup\$
  • \$\begingroup\$ Thank you so much for the thorough feedback, I appreciate it. I'm going to make the changes to my code later today. @SuperBiasedMan \$\endgroup\$ – Aaron Sep 30 '15 at 11:11
  • \$\begingroup\$ @Xarotic Glad to help! If anything is confusing or unclear, just ask. And when you've finished making changes, you're free to post another question on here as a follow up if you'd like to get feedback on that new version. \$\endgroup\$ – SuperBiasedMan Sep 30 '15 at 11:13
  • \$\begingroup\$ I couldn't wait until after class, I jumped on it this morning and I'm trying to replace what you told me with what I have working. \$\endgroup\$ – Aaron Sep 30 '15 at 11:27
  • \$\begingroup\$ Although I'm not sure If I'm improving anything or making a giant mess haha. Maybe I need to redo the whole thing with suggested code.. \$\endgroup\$ – Aaron Sep 30 '15 at 11:34
  • \$\begingroup\$ @Xarotica Sometimes restarting is the most helpful so you get a clean slate to rethink things. At least it's not a long script to restart! \$\endgroup\$ – SuperBiasedMan Sep 30 '15 at 11:39
2
\$\begingroup\$

If you're currently learning Python you should probably look at Python 3 more so than Python 2.

There are only two violations for PEP8 (caught by the command-line tool, pep8), that's very good; line 9 can be split in a multitude of ways to go below the 80 character limit and there are more than one space before the plus in the last line.

i = raw_input('Please input your type of residency, I for in-state, O for '
              'out-of-state, and G for graduate: ')

Also, constants should be uppercase with underscores, I'd say that applies to inState etc.

IN_STATE = 10000
OUT_STATE = 24000
PROF_GRAD = 40000

That said, it would be easier to use a dictionary for this kind of selection (BASE_TUITION[i]) - that might be a bit less descriptive though. Btw. i isn't very descriptive either.

Personally, I'd also stick with one kind of quotation marks, but YMMV.

Now for the code and other conventions.

First, code would usually be put into functions to make it reusable. I don't know if you're that far ahead, just to note that it would be nice to split the input, processing and output parts into separate units.

There is no error handling for invalid input. If I enter foo I'd expect a (helpful) error message from the program, not a NameError as in the current state. For example, using the following loop, the program will only handle valid input and print at least an indicator that something went wrong:

costOfTuition = None
while costOfTuition is None:
    i = raw_input('Please input your type of residency, I for in-state, O for '
                  'out-of-state, and G for graduate: ')

    if i == 'I':
        costOfTuition = IN_STATE
    elif i == 'O':
        costOfTuition = OUT_STATE
    elif i == 'G':
        costOfTuition = PROF_GRAD
    else:
        print("Invalid input.")

The while loop would be more readable using for and range. Since x isn't actually used, _ can be used instead.

for _ in range(5):
    intMath = costOfTuition * 0.03
    tuitionIncrease.append(intMath)
    fnlMath = intMath + costOfTuition
    years.append(fnlMath)
    costOfTuition = fnlMath

The assignments to academicYear1 to 5 should be a warning sign. If that happens in code it's usually an indicator that it can be more easily achieved with less variables (and less typing).

years = []
tuitionIncrease = []
academicYear = []
academicYear1Inc = []

for _ in range(5):
    intMath = costOfTuition * 0.03
    tuitionIncrease.append(intMath)
    fnlMath = intMath + costOfTuition
    years.append(fnlMath)
    costOfTuition = fnlMath
    academicYear.append("${:,.2f}".format(fnlMath))
    academicYear1Inc.append("${:,.2f}".format(intMath))

If you want it in separate loops, that's fine too. Same goes for the sum call at the end. It's nicer to look at, so I kept it around.

Lastly, the formatting is mostly good with using format. The last print statements can be written in a nicer way though.

All in all:

IN_STATE = 10000
OUT_STATE = 24000
PROF_GRAD = 40000


costOfTuition = None
while costOfTuition is None:
    residency = raw_input('Please input your type of residency, I for '
                          'in-state, O for out-of-state, and G for graduate: ')

    if residency == 'I':
        costOfTuition = IN_STATE
    elif residency == 'O':
        costOfTuition = OUT_STATE
    elif residency == 'G':
        costOfTuition = PROF_GRAD
    else:
        print("Invalid input.")

years = []
tuitionIncrease = []
academicYear = []
academicYear1Inc = []

for _ in range(5):
    intMath = costOfTuition * 0.03
    tuitionIncrease.append(intMath)
    fnlMath = intMath + costOfTuition
    years.append(fnlMath)
    costOfTuition = fnlMath
    academicYear.append("${:,.2f}".format(fnlMath))
    academicYear1Inc.append("${:,.2f}".format(intMath))

totalTuitionIncreaseSum = sum(tuitionIncrease)
totalTuitionIncrease = "${:,.2f}".format(totalTuitionIncreaseSum)

print('UNDERGRADUATE TUITION FOR THE NEXT FIVE YEARS ')
print('ACADEMIC YEAR          TUITION       INCREASE ')
print('-------------     ------------       -------- ')

for i, year in enumerate(range(16, 21)):
    print('{}-{}             {}        {}'.format(year + 2000, year + 1,
                                                  academicYear[i],
                                                  academicYear1Inc[i]))

print('TOTAL TUITION INCREASE              ' + totalTuitionIncrease)
\$\endgroup\$
  • \$\begingroup\$ Thank you very much, I think I'm going to try and merge your answer and SuperBiasedMan's answer. @ferada \$\endgroup\$ – Aaron Sep 30 '15 at 11:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.