3
\$\begingroup\$

After a bit of a break away from any coding, I decided to return with something simple. As I haven't done much of GUI programming with python, and I would like to do more, I decided to make a simple calculator. If anyone is so kind, I'd like some feedback on anything that might be improved. If it makes a difference in how you'd give feedback, I consider myself an intermediate python coder (whether that's actually true remains to be seen). Some things I'm curious about in particular:

  • Logic - Am I using any convoluted chains of logic that can be simplified?
  • General Style - I've attempted to adhere to PEP8
  • Key bindings - Is there a more effective way to bind so many keys?
  • Bugs - Did I add in some unnoticed "features"? How can I avoid them in the future?
  • (Opinion based) Tkinter: Even a good choice/something to pursue?

import tkinter as tk

from decimal import Decimal
from tkinter import ttk


class Calculator(ttk.Frame):
  def __init__(self, master=tk.Tk()):
    super().__init__(master)
    self.master = master
    self.master.title("tKalculator")
    self.operator = None
    self.prev_num = 0
    self.completed_calculation = False
    self.grid()

    self.create_widgets()
    self.bind_keys()
    self.arrange_widgets()

    self.mainloop()

  def create_widgets(self):
    """
    Create all calculator buttons
    and widgets
    """

    self.number_buttons = []

    # Number display
    self.top_display_space = ttk.Label(self, text=" ")
    self.display = tk.Text(self, height=1, width=30, pady=4)
    self.display.insert(1.0, "0")
    self.display["state"] = "disabled"
    self.bottom_display_space = ttk.Label(self, text=" ")

    # Number buttons
    for num in range(0, 10):
      num = str(num)
      self.number_buttons.append(
        # note to self: The num=num is necessary here
        ttk.Button(self, text=num, command=lambda num=num: self.update(num))
                                 )

    self.decimal_button = ttk.Button(self, text=".",
                    command=lambda: self.update("."))

    # Special Buttons
    self.clearall_button = ttk.Button(self, text="A/C", command=self.all_clear)
    self.clear_button = ttk.Button(self, text="C", command=self.clear)

    # Math operators
    self.add_button  = ttk.Button(self, text="+", command=lambda: self.math("+"))
    self.sub_button  = ttk.Button(self, text="-", command=lambda: self.math("-"))
    self.mult_button = ttk.Button(self, text="X", command=lambda: self.math("x"))
    self.div_button  = ttk.Button(self, text="/", command=lambda: self.math("/"))
    self.eql_button  = ttk.Button(self, text="=", command=lambda: self.math("="))

  def arrange_widgets(self):
    """
    Arrange all calculator widgets.
    """

    # Display
    self.top_display_space.grid(row=0, column=1)
    self.display.grid(row=1, column=0, columnspan=5)
    self.bottom_display_space.grid(row=2, column=2)

    # Number buttons
    row = 3
    column = 1
    for i in range(1, 10):
      self.number_buttons[i].grid(row=row, column=column)
      column += 1
      if column > 3:
        column = 1
        row += 1

    self.number_buttons[0].grid(row=6, column=2)
    self.decimal_button.grid(row=6, column=1)

    # Special Buttons
    self.clearall_button.grid(row=7, column=1)
    self.clear_button.grid(row=6, column=3)

    # Math operator buttons
    self.add_button.grid(row=7, column=2)
    self.sub_button.grid(row=7, column=3)
    self.mult_button.grid(row=8, column=2)
    self.div_button.grid(row=8, column=3)
    self.eql_button.grid(row=8, column=1)

  def bind_keys(self):
  """
  Binds events to keyboard button presses.
  """

    # Numbers
    self.master.bind("1", self.keypress_handler)
    self.master.bind("<KP_1>", self.keypress_handler)
    self.master.bind("2", self.keypress_handler)
    self.master.bind("<KP_2>", self.keypress_handler)
    self.master.bind("3", self.keypress_handler)
    self.master.bind("<KP_3>", self.keypress_handler)
    self.master.bind("4", self.keypress_handler)
    self.master.bind("<KP_4>", self.keypress_handler)
    self.master.bind("5", self.keypress_handler)
    self.master.bind("<KP_5>", self.keypress_handler)
    self.master.bind("6", self.keypress_handler)
    self.master.bind("<KP_6>", self.keypress_handler)
    self.master.bind("7", self.keypress_handler)
    self.master.bind("<KP_7>", self.keypress_handler)
    self.master.bind("8", self.keypress_handler)
    self.master.bind("<KP_8>", self.keypress_handler)
    self.master.bind("9", self.keypress_handler)
    self.master.bind("<KP_9>", self.keypress_handler)
    self.master.bind("0", self.keypress_handler)
    self.master.bind("<KP_0>", self.keypress_handler)
    self.master.bind(".", self.keypress_handler)
    self.master.bind("<KP_Decimal>", self.keypress_handler)

    # Special buttons
    self.master.bind("c", self.keypress_handler)
    self.master.bind("a", self.keypress_handler)
    self.master.bind("C", self.keypress_handler)
    self.master.bind("A", self.keypress_handler)
    self.master.bind("<BackSpace>", self.backspace)

    # Math operator buttons
    self.master.bind("+", self.keypress_handler)
    self.master.bind("-", self.keypress_handler)
    self.master.bind("*", self.keypress_handler)
    self.master.bind("x", self.keypress_handler)
    self.master.bind("/", self.keypress_handler)
    self.master.bind("<KP_Add>", self.keypress_handler)
    self.master.bind("<KP_Subtract>", self.keypress_handler)
    self.master.bind("KP_Multiply>", self.keypress_handler)
    self.master.bind("<KP_Divide>", self.keypress_handler)

    # Attempt to math
    self.master.bind("<KP_Enter>", self.keypress_handler)
    self.master.bind("<Return>", self.keypress_handler)
    self.master.bind("=", self.keypress_handler)

    # Escape to close the calculator
    self.master.bind("<Escape>", self.keypress_handler)

  def backspace(self, event):
  """
  Remove one character from the display.
  """

    self.display["state"] = "normal"
    current = self.display.get(1.0, tk.END)
    self.display.delete(1.0, tk.END)
    current = current[:-2]

    # Make sure that the display is never empty
    if current == "":
      current = "0"

    self.display.insert(1.0, current)
    self.display["state"] = "disabled"

  def keypress_handler(self, event):
  """
  Handles any bound keyboard presses.
  """

    char_keycode = '01234567890.'
    char_operator = "+-x*/"

    if (event.char in char_keycode):
      self.update(event.char)

    elif event.char in char_operator:
      self.math(event.char)

    elif event.char == "\r" or event.char == "=":
      self.math("=")

    elif event.char == "\x1b":
      self.master.destroy()

    elif event.char == "c" or event.char == "C":
      self.clear()

    elif event.char == "a" or event.char == "A":
      self.all_clear()

  def update(self, character):
  """
  Handles all updating of the number display.
  """
    # Allow editing of the display
    self.display["state"] = "normal"

    # Get the current number
    num = self.display.get(1.0, tk.END)
    # clear the display
    self.display.delete(1.0, tk.END)

    # Remove "\n"
    num = num.strip()

    # Clear num provided we're not putting a 
    # decimal after a zero
    if num == "0" and not character == ".":
      num = ""

    num = f"{num}{character}"

    self.display.insert(1.0, f"{num}")
    self.display["state"] = "disabled"

  def all_clear(self):
  """
  Resets everything for starting a
  new calculation.
  """
    self.clear()
    self.prev_num = 0
    self.operator = None

  def clear(self):
  """
  Clears the display by removing
  any current text and setting the
  display to 0
  """
    self.display["state"] = "normal"
    self.display.delete(1.0, tk.END)
    self.display.insert(1.0, "0")
    self.display["state"] = "disabled"

  def math(self, operator):
  """
  Handle any actual math.
  """
    if not self.operator:
      # If an operator doesn't exist, the
      # calculator is waiting for a new 
      # input.
      self.operator = operator
      self.prev_num = self.display.get(1.0, tk.END)
      self.clear()

    else:
      # The calculator is ready to do some math.
      self.prev_num = Decimal(self.prev_num)
      curr_num = self.display.get(1.0, tk.END)
      curr_num = Decimal(curr_num)

      if self.operator == "+":
        self.prev_num += curr_num
      elif self.operator == "-":
        self.prev_num -= curr_num
      elif self.operator == "x":
        self.prev_num *= curr_num
      elif self.operator == "/":
        self.prev_num /= curr_num

      self.operator = operator

      if self.operator == "=":
        # It's now time to show the current result
        # of all calculations.
        self.display["state"] = "normal"
        self.display.delete(1.0, tk.END)
        self.display.insert(1.0, str(self.prev_num))
        self.display["state"] = "disabled"
        self.completed_calculation = True
      else:
        # We're ready for another number to
        # perform calculations on
        self.clear()

if __name__ == "__main__":
  calc = Calculator()

I realized a while after posting that there is a better way to bind the keys: A for loop (duh). While I've implemented that in my updated code, I've kept the original here.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.