2
\$\begingroup\$

I made this basic Python calculator. It just does addition, subtraction, division, and multlipication.

I feel like the code is a mess, but it does what I want it to, and I feel like it does it effectively.

import operator

def calculate (first_number, operation_type, second_number):
    if operation_type == 'a':
        answer = first_number + second_number
        answer_text = f"{first_number} + {second_number} = {answer}"
    if operation_type == 's':
        answer = first_number - second_number
        answer_text = f"{first_number} - {second_number} = {answer}"
    if operation_type == 'm':
        answer = first_number * second_number
        answer_text = f"{first_number} x {second_number} = {answer}"
    if operation_type == 'd':
        answer = first_number / second_number
        answer_text = f"{first_number} \xF7 {second_number} = {answer}"
        
    return answer_text

while True:
    while True:
        try:
            first_number = int(input("Enter first number: "))
            break
        except ValueError:
            print ("Enter a number!\n")
            pass

    while True:
        operation_type = str(input("\nA - add\nS - subtract\nM - multiply\nD - divide\nEnter operation type: "))
        operation_type = operation_type.lower()
        
        if operation_type in {'a', 'm', 'd', 's'}:
            break
        else:
            print ("Invalid operation type!\n")
            pass

    while True:
        try:
            second_number = int(input("\nEnter second number: "))
            break
        except ValueError:
            print ("Enter a number!\n")
            pass

    print (calculate (first_number, operation_type, second_number))
    
    run_again = str(input("Run again? Type 'y' to continue, or q to quit: "))
    if run_again.lower() == 'y':
        pass
    if run_again.lower() == 'q':
        break
    elif run_again.lower() != 'y' and run_again.lower() != 'q':
        print ("Invalid entry")
        break
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You imported operator, but did not take advantage of it. That's unfortunate, because the module contains exactly what a simple calculator program needs: binary functions for arithmetic. Your calculate() function could reduce to this:

import operator

OPERATIONS = {
    'a': operator.add,
    's': operator.sub,
    'm': operator.mul,
    'd': operator.truediv,
}

def calculate(a, op, b):
    return OPERATIONS[op](a, b)

If code is repetitive, functions usually help. Most of your code is repetitive stuff to get user input. When you see that kind of repetition, your first instinct should be to extract out the common parts into a function. Collecting user input usually involves three things: (1) a prompt telling the user what to enter, (2) code to convert the input string into the desired type of value, and (3) code to validate that value. The "code" to convert and validate can often take the form of built-in functions/methods (for example, int, float, or str.lower) or, when needed, short functions or lambdas that you define. Here's one way to implement a user-input function in a fairly generalized way applicable to many programs:

def get_input(prompt, convert, validate):
    invalid_msg = 'Invalid input.'
    while True:
        reply = input(prompt + ': ')
        try:
            val = convert(reply)
            if validate(val):
                return val
            else:
                print(invalid_msg)
        except Exception as e:
            print(invalid_msg)

Put all code in functions. Even in small programs. Your calculator code becomes fairly simple once you have a user-input function to handle the grubby details.

def main():
    op_prompt = '\nA - add\nS - subtract\nM - multiply\nD - divide\nEnter operation'
    op_is_valid = lambda x: x in OPERATIONS
    yn_is_valid = lambda x: x in 'yn'
    always_valid = lambda x: True
    while True:
        a = get_input('Enter first number', int, always_valid)
        op = get_input(op_prompt, str.lower, op_is_valid)
        b = get_input('Enter second number', int, always_valid)
        result = calculate(a, op, b)
        print(result)
        if get_input('Run again [y/n]', str.lower, yn_is_valid) == 'n':
            break

if __name__ == '__main__':
    main()

Next steps: better user experience. (1) This code above does not handle the division-by-zero scenario, but you could easily make that adjustment. (2) Support floats: no good reason not to. (3) Consider simplifying the burden on the user: instead of forcing them to engage in four discrete dialogues with the computer (first number? operator? second number? continue?), which becomes tedious very rapidly, just let them type a normal arithmetic expression (delimited by spaces) and interpret a blank entry as the signal for quit. Here's what a session might look like.

Simple calculator:
12 + 13
=> 25
5 - 3
=> 2
3 / 0
=> Error: division by zero
9 * 9
=> 81

Goodbye.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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