0

I have been tasked with creating a python program that will ask for user inputs and calculate monthly loan repayments, this is the formula I have to work off: Formula. Doing this was not too difficult, however the tutor asked as a bonus to try to make the user inputs 'unbreakable', meaning if any value other than the expected were input it wouldn't break the program. I thought this can easily be done with Try Except, and that's what I did, however I believe my code can be written in a much more concise manner, instead of a Try Except for every input as below:

err = "Please enter a number only!!"
while True:
    try:
        A = int(input("How much were you loaned? "))
    except ValueError:
        print(err)
        continue
    else:
        break
while True:
    try:
        R = float(input("At what monthly rate interest? ")) / 100
    except ValueError:
        print(err)
        continue
    else:
        break
while True:
    try:
        N = int(input("And how many years is the loan for? "))
    except ValueError:
        print(err)
        continue
    else:
        break
RA = R * A
RN = 1 - (1 + R) ** -N
P = RA / RN
print("You will pay £", P, "yearly", "or, £", P / 12, "monthly") 

I feel as if perhaps the user inputs could be put into a For Loop, or perhaps all of this into one Try Except block? After thinking about it, I put all the inputs into one Try Except block, however as soon as you fail to correctly input the expected data for the user input, it goes right back to the beginning, not to the question you were on. This is not what I want. Have a look:

err = "Please enter a number only!!"
while True:
    try:
        A = int(input("How much were you loaned? "))
        R = float(input("At what monthly rate interest? ")) / 100
        N = int(input("And how many years is the loan for? "))
    except ValueError:
        print(err)
        continue
    else:
        break
RA = R * A
RN = 1 - (1 + R) ** -N
P = RA / RN
print("You will pay £", P, "yearly", "or, £", P / 12, "monthly")

How do you think I can modify this code to make it more concise, shorter, and efficient without having a Try Except for every user input?

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Mr Noodles
  • 11
  • 1
  • 1
    Any time you are writing nearly-identical code blocks, the solution is to move that block into a function, and call it with parameters that specify the unique details of each original block. – jasonharper Oct 14 '21 at 19:38
  • Try doing only through while loop. Write while True and if type(A)==int and type(R)==float and type(N)==int: break else: continue. I've written it casually, write with proper indenation –  Oct 14 '21 at 19:45
  • Does this answer your question? [Asking the user for input until they give a valid response](https://stackoverflow.com/questions/23294658/asking-the-user-for-input-until-they-give-a-valid-response) – Tomerikoo Oct 16 '21 at 14:50

2 Answers2

2

Modularise it, and use a generic function that handles input and loops until the user enters what is asked.

def get_num(prompt="Enter number: "):
    """
    (str) -> num
    Outputs a prompt and loops until a valid number is
    entered by the user, at which point that value is
    returned and the function terminates
    """
    while True:
        num = input(prompt)
        try:
            num = float(num)
            return num
        except:
            print("Must enter a valid number")

def do_calc():
    """(None) -> None
    Get user input, perform calculations and output results
    """
    A = int(get_num("How much were you loaned? "))
    R = get_num("At what monthly rate interest? ") / 100
    N = int(get_num("And how many years is the loan for? "))
    RA = R * A
    RN = 1 - (1 + R) ** -N
    P = RA / RN
    print(f"You will pay £{round(P, 2)} yearly or £{P/12:.2f} monthly")

do_calc()

Using the f-string from Python 3 is really nice and elegant- just plug in the values or calculations inside the curly braces. As per suggestion in comment, you may wish to show to two decimal places using either the round function or the formatting option as shown.

A more pythonic way to name your variables would be

  • amount rather than A
  • rate rather than R
  • num_years rather than N and so on.
srattigan
  • 665
  • 5
  • 17
  • 1
    +1 for recommending f strings. As an addendum to that point: You can still include formatting statements inside f strings. Ex.: `f"{P/12:.2f}"` will print the value of `P/12` formatted as a floating point number with two decimal figures. – Antimon Oct 14 '21 at 20:38
  • @Antimon thanks, I've added that as an improvement. – srattigan Oct 16 '21 at 18:20
0

Extract the repeated procedure in a function, with all the relevant inputs.
I have added some formatting to the final output to avoid too many decimals

def get_value(message, func, denominator = 1):
   try_again = True
   v = None
   while try_again:
      try:
         v = func(input(message))/denominator
      except ValueError:
         print("Please enter a number only!!")
      else:
         try_again= False   
   return v

A = get_value("How much were you loaned? ", int)
R = get_value("At what monthly rate interest? ", float, denominator=100.0)
N = get_value("And how many years is the loan for? ", int)

RA = R * A
RN = 1 - (1 + R) ** -N
P = RA / RN
print("You will pay £ {:.2f} yearly or £ {:.2f} monthly".format(P, P / 12)) 
nikeros
  • 3,302
  • 2
  • 10
  • 26
  • 2
    You can simplify this greatly by just `return`ing directly from the `try` block. No need for `v`, `try_again`, or the `else` block: just an infinite `while True` loop that runs until no `ValueError` is raised. – chepner Oct 14 '21 at 20:06
  • 1
    Also, don't try to include the division in the `try` block; you've introduced the possibility of a division-by-zero error that wasn't in the OP's original code. Let `get_value` worry about returning an `int` or `float`, and let the caller divide the return value by 100 when appropriate. – chepner Oct 14 '21 at 20:08
  • @chepner denominator is not a user input. I think that it is safe to assume that the function is not called specifying denominator=0 – nikeros Oct 14 '21 at 20:49
  • It's even safer to assume if the option to do so doesn't exist. The job of `get_value` should be to parse and validate user input, not do additional processing *with* that input. – chepner Oct 14 '21 at 20:55
  • Do you really think `get_value("...", float, denominator=100.0)` is an improvement over `get_value("...", float) / 100.0`? – chepner Oct 14 '21 at 20:55
  • @chepner `get_value("...", float) / 100.0` is the starting point I wanted to improve. My point is just the denominator cannot be set to 0 at runtime, so it is not breakable in that sense. I take the point of the first comment - that makes the code much more concise and readable! +1 and thanks – nikeros Oct 14 '21 at 21:05