HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Simple Calculator with customizable button layout

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplewithlayoutcustomizablebuttoncalculator

Problem

How can I improve the code? Specifically, is there any way that I can move the code from App.handle into separate functions?

```
from tkinter import *
import operator

# tuple of buttons in the calculator
# string = command
# function = operation
# number = number input
BUTTONS = (
(('E', 'exit'), ('<-', 'backspace'), ('CE', 'clear'), ('/', operator.truediv)),
(('7', 7), ('8', 8), ('9', 9), ('*', operator.mul)),
(('4', 4), ('5', 5), ('6', 6), ('-', operator.sub)),
(('1', 1), ('2', 2), ('3', 3), ('+', operator.add)),
(('.', 'decimal'), ('0', 0), ('^', operator.pow), ('=', 'evaluate'))
)

# transpose the list
BUTTONS = zip(*BUTTONS)

def get_font(size):
"""
Return a font tuple
"""
return ('Verdana', size)

class App(Frame):
def __init__(self, parent):
super().__init__(parent)
self.pack()

self.top_bar = Frame(self)
self.top_bar.pack(fill=X, side=TOP, pady=20)

self.big_num = Label(self.top_bar, text='', font=get_font(32))
self.big_num.pack(fill=BOTH, expand=True)

self.reset_calc()

self.button_container = Frame()
self.button_container.pack(side=BOTTOM, expand=True, fill=BOTH)
for column in BUTTONS:
frame = Frame(self.button_container)
frame.pack(fill=BOTH, expand=True, side=LEFT)
for item in column:
button = Button(frame, text=item[0], font=get_font(11),
command=lambda x=item[1]: self.handle(x))
button.pack(fill=BOTH, expand=True, side=TOP)

def reset_calc(self):
"""
Reset everything.
"""
self.operator_function = None
self.first_number = None
self.set_text('')
self.displaying_solution = False

# 3 functions to modify calculator bar text
def get_text(self):
return self.big_num.cget('text')

def set_text(self, text):
self.big_num.config(text=text)

def append_te

Solution

There are a lot of areas where you could improve your code, but I'm going to focus on simplifying the handlers.

Connect your buttons to what they do

It's generally good practice to tie your buttons to their respective handlers. That way you can see what button does what while also allowing your code to be more freely modified (buttons getting added, changed, or removed). Here's a basic idea of what I mean:

def create_button(key, handler):
    return {
        key: key,
        handler: handler
    }

BUTTONS = {
  exit: create_button('E', App.exit),
  backspace: create_button('<-', App.backspace),
  clear: create_button('cl', App.reset_calc),
  # ...

  0: create_button('0', lambda app: App.input(app, 0)),
  1: create_button('1', lambda app: App.input(app, 1)),
  2: create_button('2', lambda app: App.input(app, 2)),
  # ...
}

LAYOUT = (
    (BUTTONS.exit, BUTTONS.backspace, BUTTONS.clear, BUTTONS.div),
    (BUTTONS[7], BUTTONS[8], BUTTONS[9], BUTTONS.mul),
    (BUTTONS[4], BUTTONS[5], BUTTONS[6], BUTTONS.sub),
    (BUTTONS[1], BUTTONS[2], BUTTONS[3], BUTTONS.add),
    (BUTTONS[1].decimal, BUTTONS[0], BUTTONS.pow, BUTTONS.evalaute)
)


This way new buttons can be added to the BUTTONS dictionary, and if you want to change the LAYOUT, you can easily switch which buttons go where.

It also comes with the added benefit that you'll be able to move your code in App.handle into seperate functions and won't need to do all the checks: if is a string or if is an int or is equal to 'exit' or .... All you need is the following change to the App constructor:

for item in column:
    button = Button(frame, text=item.key, font=get_font(11),
        command=lambda h=item.handle: h(self))


Breaking up the handle method

Add the required functionality to the App class so that the button's handlers will be able to access it:

class App(Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent # ChatterOne commented on this for exit
        # ...
    # ...
    def exit(self):
        self.parent.quit()

    def input(self, var):
        if self.displaying_solution:
            self.reset_calc()
        self.append_text(str(var))

Code Snippets

def create_button(key, handler):
    return {
        key: key,
        handler: handler
    }

BUTTONS = {
  exit: create_button('E', App.exit),
  backspace: create_button('<-', App.backspace),
  clear: create_button('cl', App.reset_calc),
  # ...

  0: create_button('0', lambda app: App.input(app, 0)),
  1: create_button('1', lambda app: App.input(app, 1)),
  2: create_button('2', lambda app: App.input(app, 2)),
  # ...
}

LAYOUT = (
    (BUTTONS.exit, BUTTONS.backspace, BUTTONS.clear, BUTTONS.div),
    (BUTTONS[7], BUTTONS[8], BUTTONS[9], BUTTONS.mul),
    (BUTTONS[4], BUTTONS[5], BUTTONS[6], BUTTONS.sub),
    (BUTTONS[1], BUTTONS[2], BUTTONS[3], BUTTONS.add),
    (BUTTONS[1].decimal, BUTTONS[0], BUTTONS.pow, BUTTONS.evalaute)
)
for item in column:
    button = Button(frame, text=item.key, font=get_font(11),
        command=lambda h=item.handle: h(self))
class App(Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent # ChatterOne commented on this for exit
        # ...
    # ...
    def exit(self):
        self.parent.quit()

    def input(self, var):
        if self.displaying_solution:
            self.reset_calc()
        self.append_text(str(var))

Context

StackExchange Code Review Q#161031, answer score: 2

Revisions (0)

No revisions yet.