Simple calculator (going H.A.M with defining functions)

I am toying around and playing a lot with defining a function. I'm trying to get extremely comfortable with it. The code I have wrote works fairly well, but definitely is not perfect. Also, the function I made called playagain is having some errors. When I type in "Yes" for the input I'm getting a strange TypeError. If you can fix that, please explain to me how, in the simplest way possible.

import sys

def userinput():
while True:
try:
number = int(input("Number: "))
break
except ValueError:
print("NOPE...")
return number

def operation():
while True:
if operation in OPTIONS:
break
else:
print("Not an option.")
return operation

def playagain():
while True:
again = input("Again? Yes/No: ")
if again == "Yes" or again == "yes":
break
elif again == "No" or again == "no":
sys.exit(0)
else:
print("Nope..")

def multiply(x,y):
z = x * y
print(z)

def divide(x,y):
z = x / y
print(z)

z = x + y
print(z)

def subtract(x,y):
z = x - y
print(z)

while True:
operation = operation()
x = userinput()
y = userinput()
elif operation == "divide" or operation == "Divide":
divide(x,y)
elif operation == "multiply" or operation == "Multiply":
multiply(x,y)
elif operation == "subtract" or operation == "Subtract":
subtract(x,y)

playagain()

I tried defining as much functions as possible to make it simpler and easier to fix and debug. Please let me know what you think.

NOTE: I am not making this program to actually be a beneficial program. I am simply using it to help myself with the defining a function portion of Python.

• possible duplicate of Python Calculator Sep 15 '13 at 8:13
• @200_success It's not a duplicate, but a follow-up question. The code is not the same, but an already much improved version of the code in the previous question. Sep 15 '13 at 8:42
• @tobias_k I recognize and applaud the improvements, but it's still fundamentally the same question, and any answers to this question will be very similar to those of the predecessor. There are also plenty of suggestions in previous answers that have not been incorporated yet. Sep 15 '13 at 8:49

Of the feedback on your earlier version of this programme, the most obvious thing that you haven't done yet is to use lower() on text inputs so that you only have to compare against lower case options. e.g.

again = input("Again? Yes/No: ").lower()
if again == "yes":
break
elif again == "no":
sys.exit(0)

It might be useful to know that the operations you define already exist as methods of numeric variables in Python, called __mul__, __add__, etc., and are also available within the operator module. e.g. operator.add(3, 4) is equivalent to 3 + 4. So you could, for example, do this

import operator

OPTIONS = {
'divide': operator.truediv,
'multiply': operator.mul,
'subtract': operator.sub
}

def user_input():
while True:
try:
return int(input("Number: "))
except ValueError:
print("NOPE...")

def get_operation():
while True:
try:
return OPTIONS[operation]
except KeyError:
print("Not an option.")

def play_again():
while True:
again = input("Again? Yes/No: ").lower()
if again == "yes":
return True
elif again == "no":
return False
else:
print("Nope..")

while True:
operation = get_operation()
x = user_input()
y = user_input()
print(operation(x, y))
if not play_again():
break
• Your code didn't work for me, but it's still a very nice answer. I have provided corrections in a separate answer. Sep 15 '13 at 11:15
• Thanks, I was mixing up Python 2 and 3. I corrected it above to Python 3 as that is what the question uses. Sep 15 '13 at 11:31
• Thanks @Stuart I appreciate your answer! I will definitely start using the .lower() , I never really knew about it or knew what it did. I will study your code and make some changes. Sep 15 '13 at 18:10

Stuart's code is very nicely written, and provides much to a learner, but it didn't work for me. Fixed versions are too long for just a comment, so here they are.

Python 2

The exact version I use: 2.7.3

import operator

OPTIONS = {
'divide': operator.div,
'multiply': operator.mul,
'subtract': operator.sub
}

def user_input():
while True:
try:
return input("Number: ")
except ValueError:
print("NOPE...")

def get_operation():
while True:
try:
return OPTIONS[operation]
except KeyError:
print("Not an option.")

def play_again():
while True:
again = raw_input("Again? Yes/No: ").lower()
if again == "yes":
return True
elif again == "no":
return False
else:
print("Nope..")

while True:
operation = get_operation()
x = user_input()
y = user_input()
print operation(x, y)
if not play_again():
break

In Python 2, input returns an integer, so int(input(...)) is redundant, and inputting string (for operation and going again) fails. The later is fixed by using raw_input.

Python 3

The exact version I use: 3.2.3

import operator

OPTIONS = {
'divide': operator.truediv,
'multiply': operator.mul,
'subtract': operator.sub
}

def user_input():
while True:
try:
return int(input("Number: "))
except ValueError:
print("NOPE...")

def get_operation():
while True:
try:
return OPTIONS[operation]
except KeyError:
print("Not an option.")

def play_again():
while True:
again = input("Again? Yes/No: ").lower()
if again == "yes":
return True
elif again == "no":
return False
else:
print("Nope..")

while True:
operation = get_operation()
x = user_input()
y = user_input()
print(operation(x, y))
if not play_again():
break

The obvious error here was the last print statement: it's a function in Python 3, so print operation(x, y) had to be replaced with print(operation(x, y)).

The less obvious one is operator.div which doesn't exist in Python 3, but was instead replaced by operator.truediv and operator.floordiv. In the spirit of this program, I assumed that the former is what was intended.

I do not quite understand the question, however, I am assuming that you would like to build a calculator application. I have quickly "whipped up" this code. It is compatible with python3, and comes with a graphical user interphase.

Instead of creating that huge function to determine the operator, just use the eval() function instead in a GUI.

As seen in the function Calculate(), I am actually getting the text input from the display screen and just evaluating it. I also error check using a try and except statement.

Calculator with a GUI:

from tkinter import *
from tkinter import ttk
import time
import random

tk = Tk()
canvas = Canvas(tk, height=400, width=300, bg = 'white')
canvas.pack()
tk.title('Calculator')
tk.resizable(False, False)

class Calculator:
def Title(self):
title = Label(tk, text = 'Calculator', font='Times, 20')
title.place(x = 0, y = 10)
def EntryBar(self):
self.CalculationBox = Text(tk, height=2, width=27, font='Times, 15', bg = 'gray60')
self.CalculationBox.place(x = 0, y = 60)
self.counter = 1

def DisplayCal(self, num):
self.CalculationBox.insert(END, num)

def Calculate(self):
try:
self.CalculationBox.delete("1.0", END)
except Exception:
self.CalculationBox.delete("1.0", END)
self.CalculationBox.insert(END, "Error")

c = Calculator()

class Buttons:
def Buttons(self):
self.ZeroBtn = Button(tk, text = '0', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=0))
self.ZeroBtn.place(x = 120, y = 360)
self.OneBtn = Button(tk, text = '1', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=1))
self.OneBtn.place(x = 53, y = 320)
self.TwoBtn = Button(tk, text = '2', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=2))
self.TwoBtn.place(x = 120, y = 320)
self.ThreeBtn = Button(tk, text = '3', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=3))
self.ThreeBtn.place(x = 187, y = 320)
self.FourBtn = Button(tk, text = '4', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=4))
self.FourBtn.place(x = 53, y = 280)
self.FiveBtn = Button(tk, text = '5', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=5))
self.FiveBtn.place(x = 120, y = 280)
self.SixBtn = Button(tk, text = '6', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=6))
self.SixBtn.place(x = 187, y = 280)
self.SevenBtn = Button(tk, text = '7', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=7))
self.SevenBtn.place(x = 53, y = 240)
self.EightBtn = Button(tk, text = '8', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=8))
self.EightBtn.place(x = 120, y = 240)
self.NineBtn = Button(tk, text = '9', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num=9))
self.NineBtn.place(x = 187, y = 240)
self.AddBtn = Button(tk, text = '+', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num='+'))
self.AddBtn.place(x = 53, y = 200)
self.MultBtn = Button(tk, text = 'x', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num='*'))
self.MultBtn.place(x = 120, y = 200)
self.DivBtn = Button(tk, text = '÷', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num='/'))
self.DivBtn.place(x = 187, y = 200)
self.SubBtn = Button(tk, text = '-', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.DisplayCal(num='-'))
self.SubBtn.place(x = 53, y = 160)
self.EquBtn = Button(tk, text = '=', height=2, width=8, bg = 'gray60', fg = 'white', command = c.Calculate)
self.EquBtn.place(x = 187, y = 160)
self.ClsBtn = Button(tk, text='Clear', height=2, width=8, bg = 'gray60', fg = 'white', command = lambda: c.CalculationBox.delete('1.0',END))
self.ClsBtn.place(x = 120, y = 160)
b = Buttons()
def main():
c.Title()
c.EntryBar()
b.Buttons()

main()

tk.mainloop()