5
\$\begingroup\$

Made a very simple calculator in Python. The program has the following features:

  1. All the basic operators can be used. '+', '-', '*', '**', '/', and '//'.
  2. The user can start the program again.
  3. The previous output is available for the user to use again.
def start(out='temp'):
    # Get the input from the user and pass all the values to verify.
    # Explicit test is used because the output can be zero.
    if out != 'temp':
        x = out
        print('\nFirst number: ', x)
    else:
        x = input('\nFirst number: ')
    op = input('Operator: ')
    y = input('Second number: ')
    verify(x, op, y)

def verify(x, op, y):
    # Check if the operator and the numbers entered are valid.
    # If any input is invalid call start and get input again.
    # If inputs are valid pass all the values to calc.
    ops = ['+', '-', '*', '**', '/', '//']
    if op in ops:
        try:
            x, y = int(x), int(y)
        except ValueError:
            print('Numbers are not valid.\n')
            start()
        else:
            calc(x, op, y)            
    else:
        print('Please enter a valid operator.\n')
        start()

def calc(x, op, y):
    # Use eval to calculate the output and pass the output to 
    # restart.
    out = eval(f'x {op} y')
    print('Output:', out)
    restart(out)

def restart(out):
    # User can start the process again. The previous output can be used as
    # the first number.
    re = input('Start again? (y/n): ')
    if re == 'y':
        use_out = input(
            'Use the previous output as the first number? (y/n): ')
        if use_out == 'y':
            start(out=out)
        else:
            start()
    else:
        print('Calc is now closed.')

start()

Here's a sample output.

First number: 5
Operator: *
Second number: 9
Output: 45
Start again? (y/n): y
Use the previous output as the first number? (y/n): y

First number:  45
Operator: //
Second number: 2
Output: 22
Start again? (y/n): n
Calc is now closed.

I am looking for ways to make the code more readable, minimize redundancy, improving the overall design, etc. Any help would be appreciated!

\$\endgroup\$
4
\$\begingroup\$

Your program is easy to read and does all the necessary input validation. That's good.

The error messages should be more helpful. If the user enters an invalid operator, you should tell them which operators are valid:

print(f'Please enter a valid operator: {' '.join(ops)}\n')

Every use of eval is dangerous. If you pass unvalidated input to it, users might be able to run arbitrary Python code. You currently do the validation in verify and the actual calculation in calc. That's nicely separated, but it can also lead to a situation where you later call calc by accident with unvalidated input. To avoid this, most calculator programs use a dictionary of operators:

binops = {
    '+': lambda a, b: return a + b,
    '-': lambda a, b: return a - b,
    # and so on
}

try:
    return binops[op](x, y)
except KeyError, e:
    print(f'Invalid operator {op!r}, valid operators are {sorted(ops.keys())}')

One unfortunate thing about the above code is that ops.keys() returns the operators in an unspecified order. Therefore I had to add the sorted call.

Each of your functions calls the continuation function at the end. When you try a long session with the calculator (about 5000 calculations), it will raise an exception. Whenever a function is called, Python remembers where it was called from, and it can remember only a few nested function calls. Therefore it is more common to use while loops for representing repetition. See https://stackoverflow.com/q/1359197 for more details.

As a user of the calculator, I don't like to enter the numbers and operators separately. As the next step the calculator should allow inputs like 3+5 and 7 - -9 and +7--9. You can do this by using regular expressions.

The current code asks many questions individually. Instead of asking whether to reuse the result from the last calculation, you could print the result in the form ans1 = 8 and allow the user to write expressions like ans4 * ans5. A calculator session might then look like:

> 123
ans1 = 123

> 456
ans1 = 456

> 3 + ans1
ans3 = 126

> ans3 + ans3
ans4 = 252

> result = ans4
result = 252

> result + result
ans5 = 504

This way the calculator remembers all previous results, and by using the = operator, you can name individual results and refer to them via that name, just like variables in Python. All you need for this is a dictionary and a counter (for the automatic variable names like ans4):

vars = {}
ans = 0

def store_var(name, value):
    vars[name] = value

def store_auto_var(value):
    global ans
    ans += 1
    name = 'ans' + ans
    vars[name] = value

These are the basic building blocks for building a really powerful calculator.

\$\endgroup\$
  • 2
    \$\begingroup\$ A better solution to using regular expressions to parse the input is to use the shuntung yard algorithm. \$\endgroup\$ – Soupy May 12 '19 at 5:36

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.