Skip to content

Instantly share code, notes, and snippets.

@lucasbflopes
Last active August 13, 2025 00:28
Show Gist options
  • Save lucasbflopes/a60f0d551d04a9769ef01b98972c9ac0 to your computer and use it in GitHub Desktop.
Save lucasbflopes/a60f0d551d04a9769ef01b98972c9ac0 to your computer and use it in GitHub Desktop.
An RPN Calculator in Python with GUI
import math
try:
from Tkinter import * # python 2.x
except ImportError:
from tkinter import * # python 3.x
class Calculator(Frame):
digits = [str(i) for i in range(10)] + ["."]
operators = ["+", "-", "*", "/", "1/x", "+/-", "sin", "cos", "tan", "y^x", "%", "e^x", "log"]
others = ["C", "ENTER", "AC", "pi", "SWAP", "POP"]
font1 = "Verdana 11"
font2 = "Verdana 16"
buttons = dict()
operationPressed = False
stack = []
isAnErrorHappened = False
errorMessages = {"stackError": "Error: not enough args in stack",
"divisionByZero": "Error: division by zero ",
"domainError": "Error: cannot complete calculation",
"overflow": "Error: oveflow"}
def __init__(self, master):
Frame.__init__(self, master)
master.wm_title("RPN Calculator v1.0")
master.resizable(width=False, height=False)
self.pack()
self.create_widgets()
self.bind_keys(master)
def create_widgets(self):
self.onDisplay = StringVar()
self.onDisplay.set("")
self.onStackDisplay = StringVar()
self.onStackDisplay.set("[ ]")
self.isDegrees = BooleanVar()
self.isDegrees.set(True)
self.display = Label(self,
textvariable=self.onDisplay,
font=self.font2,
bg="white",
borderwidth=5,
relief="groove",
width=48,
height=2,
anchor=E)
self.stackLabel = Label(self,
text="Stack:",
font=self.font1,
width=8,
anchor=W,
height=2)
self.stackDisplay = Label(self,
textvariable=self.onStackDisplay,
font=self.font1,
fg="royal blue",
width=32,
anchor=W,
height=2)
for digit in self.digits:
self.buttons[digit] = Button(self,
text=digit,
font=self.font1,
command=lambda d=digit: self.add_to_display(d),
width=8,
height=2)
for operator in self.operators:
self.buttons[operator] = Button(self,
text=operator,
font=self.font1,
command=lambda o=operator: self.perform_operation(o),
width=8,
height=2)
for other in self.others:
if other == "ENTER":
command = self.press_ENTER
elif other == "AC":
command = self.press_AC
elif other == "C":
command = self.press_C
elif other == "pi":
command = self.press_PI
elif other == "POP":
command = self.press_POP
elif other == "SWAP":
command = self.press_SWAP
self.buttons[other] = Button(self,
text=other,
font=self.font1,
command=command,
width=8,
height=2)
self.degreesRadioButton = Radiobutton(self,
text="DEG",
width=8,
height=2,
variable=self.isDegrees,
value=True
)
self.radiansRadioButton = Radiobutton(self,
text="RAD",
width=8,
height=2,
variable=self.isDegrees,
value=False
)
self.render_layout()
def render_layout(self):
self.display.grid(row=0, column=0, columnspan=6, sticky=W)
self.stackLabel.grid(row=1, column=0, sticky=W)
self.stackDisplay.grid(row=1, column=1, columnspan=3, sticky=W)
self.buttons["AC"].grid(row=2, column=0, sticky=W)
self.buttons["C"].grid(row=2, column=1, sticky=W)
self.buttons["ENTER"].grid(row=6, column=3, sticky=W)
self.buttons["POP"].grid(row=1, column=4, sticky=W)
self.buttons["SWAP"].grid(row=1, column=5, sticky=W)
self.buttons["."].grid(row=6, column=1, sticky=W)
self.buttons["0"].grid(row=6, column=0, sticky=W)
self.buttons["1"].grid(row=5, column=0, sticky=W)
self.buttons["2"].grid(row=5, column=1, sticky=W)
self.buttons["3"].grid(row=5, column=2, sticky=W)
self.buttons["4"].grid(row=4, column=0, sticky=W)
self.buttons["5"].grid(row=4, column=1, sticky=W)
self.buttons["6"].grid(row=4, column=2, sticky=W)
self.buttons["7"].grid(row=3, column=0, sticky=W)
self.buttons["8"].grid(row=3, column=1, sticky=W)
self.buttons["9"].grid(row=3, column=2, sticky=W)
self.buttons["*"].grid(row=2, column=3, sticky=W)
self.buttons["/"].grid(row=3, column=3, sticky=W)
self.buttons["+"].grid(row=4, column=3, sticky=W)
self.buttons["-"].grid(row=5, column=3, sticky=W)
self.buttons["1/x"].grid(row=4, column=4, sticky=W)
self.buttons["+/-"].grid(row=6, column=2, sticky=W)
self.buttons["%"].grid(row=2, column=2, sticky=W)
self.buttons["y^x"].grid(row=5, column=4, sticky=W)
self.buttons["sin"].grid(row=2, column=5, sticky=W)
self.buttons["cos"].grid(row=3, column=5, sticky=W)
self.buttons["tan"].grid(row=4, column=5, sticky=W)
self.buttons["pi"].grid(row=5, column=5, sticky=W)
self.buttons["e^x"].grid(row=2, column=4, sticky=W)
self.buttons["log"].grid(row=3, column=4, sticky=W)
self.degreesRadioButton.grid(row=6, column=4, sticky=W)
self.radiansRadioButton.grid(row=6, column=5, sticky=W)
def bind_keys(self, master):
for digit in self.digits:
master.bind(digit, self.add_to_display)
master.bind(".", self.add_to_display)
master.bind("<Return>", self.press_ENTER)
master.bind("+", self.perform_operation)
master.bind("-", self.perform_operation)
master.bind("*", self.perform_operation)
master.bind("/", self.perform_operation)
master.bind("%", self.perform_operation)
master.bind("<BackSpace>", self.press_DELETE)
def add_to_display(self, value):
if self.isAnErrorHappened:
self.press_C()
if self.operationPressed:
self.press_ENTER()
self.press_C()
if isinstance(value, Event): # Input via keyboard rather than clicking on app
value = value.char
if not(value == "." and value in self.onDisplay.get()):
self.onDisplay.set(self.onDisplay.get() + value)
self.operationPressed = False
self.isAnErrorHappened = False
def press_DELETE(self, event=None):
if self.onDisplay.get() not in self.errorMessages.values():
self.onDisplay.set(self.onDisplay.get()[:-1])
else:
self.press_C()
def press_SWAP(self):
if self.onDisplay.get() and len(self.stack) > 0:
b, self.stack[-1] = self.stack[-1], self.onDisplay.get(),
self.onDisplay.set(b)
self.refresh_stack_display()
def press_POP(self):
if len(self.stack) > 0:
self.stack.pop()
self.refresh_stack_display()
def press_C(self):
self.onDisplay.set("")
def press_PI(self):
self.press_ENTER()
self.onDisplay.set("{}".format(math.pi))
def refresh_stack_display(self):
self.onStackDisplay.set("[ {} ]".format(", ".join(self.stack[::-1])))
def press_ENTER(self, event=None):
if self.onDisplay.get() and self.onDisplay.get() not in self.errorMessages.values():
self.stack.append(self.onDisplay.get())
self.press_C()
self.refresh_stack_display()
def press_AC(self):
self.stack = []
self.press_C()
self.refresh_stack_display()
def perform_operation(self, operator):
if self.onDisplay.get() in self.errorMessages.values():
return
try:
if isinstance(operator, Event): # Input via keyboard rather than clicking on app
operator = operator.char
if operator in ["+", "-", "*", "/", "y^x", "%"]:
if self.onDisplay.get():
x = float(self.onDisplay.get())
y = float(self.stack.pop())
else:
x = float(self.stack.pop())
y = float(self.stack.pop())
if operator == "+":
self.onDisplay.set("{}".format(round(y + x, 10)))
elif operator == "-":
self.onDisplay.set("{}".format(round(y - x, 10)))
elif operator == "*":
self.onDisplay.set("{}".format(round(y * x, 10)))
elif operator == "/":
self.onDisplay.set("{}".format(round(y / x, 10)))
elif operator == "y^x":
self.onDisplay.set("{}".format(round(y**x, 10)))
elif operator == "%":
self.onDisplay.set("{}".format(round(x/100.0 * y, 10)))
else:
if self.onDisplay.get():
x = float(self.onDisplay.get())
else:
x = float(self.stack.pop())
if operator == "1/x":
self.onDisplay.set("{}".format(round(x**-1, 10)))
elif operator == "+/-":
self.onDisplay.set("{}".format(round(-x, 10)))
elif operator == "sin":
conversionFactor = math.pi/180 if self.isDegrees.get() else 1
self.onDisplay.set("{}".format(round(math.sin(conversionFactor * x), 10)))
elif operator == "cos":
conversionFactor = math.pi/180 if self.isDegrees.get() else 1
self.onDisplay.set("{}".format(round(math.cos(conversionFactor * x), 10)))
elif operator == "tan":
conversionFactor = math.pi/180 if self.isDegrees.get() else 1
self.onDisplay.set("{}".format(round(math.tan(conversionFactor * x), 10)))
elif operator == "e^x":
self.onDisplay.set("{}".format(round(math.exp(x), 10)))
elif operator == "log":
self.onDisplay.set("{}".format(round(math.log(x), 10)))
self.operationPressed = True
self.isAnErrorHappened = False
self.refresh_stack_display()
except IndexError: # There isn't enough args in the stack to do the operation
self.press_ENTER()
self.onDisplay.set(self.errorMessages["stackError"])
self.isAnErrorHappened = True
self.refresh_stack_display()
except ZeroDivisionError: # Division by zero
self.onDisplay.set(self.errorMessages["divisionByZero"])
self.isAnErrorHappened = True
self.refresh_stack_display()
except ValueError:
self.onDisplay.set(self.errorMessages["domainError"])
self.isAnErrorHappened = True
self.refresh_stack_display()
except OverflowError:
self.onDisplay.set(self.errorMessages["overflow"])
self.isAnErrorHappened = True
self.refresh_stack_display()
if __name__ == "__main__":
root = Tk()
app = Calculator(root)
app.mainloop()
@bru32
Copy link

bru32 commented Aug 13, 2025

Nice example.
I have a question. You declare a number of class variables. But why do you reference them in the code as though they were instance variables?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment