I made a Calculator with 20 lines of code using wx

Hi, there :wave:

I recently found an interesting post that says “I made a Calculator with 17 lines of code”
Inspired by this, I tried to make a calculator using wxPython with minimal code.

First, the original code using Tkinter (slightly modified):

import tkinter as tk

def bt_draw(key, col, lin):
    bt = tk.Button(window, text=key, command=lambda: bt_press(key), width=5)
    bt.grid(column=col+1, row=lin+1)
    return bt

def bt_press(key):
    if   key == 'C': disp['text'] = ''
    elif key == '<': disp['text'] = disp['text'][:-1]
    elif key == '=': disp['text'] = str(round(eval(disp['text']),16))
    else           : disp['text'] += key

window = tk.Tk()
window.title('Tkalc')

disp = tk.Label(window, text='')
disp.grid(column=0, row=0, columnspan=5)

keys = '()C<789/456*123-.0=+'
bt_list = [bt_draw(keys[n], n%4, n//4) for n in range(20)]

window.mainloop()

Second, using pySimpleGUI, it becomes 17 lines too.

import PySimpleGUI as sg

def bt_press(key):
    text = window['display'].DisplayText
    if   key == 'C': text = ''
    elif key == '<': text = text[:-1]
    elif key == '=': text = str(round(eval(text),16))
    else           : text += key or ''
    window['display'].update(text)

keys = '()C<789/456*123-.0=+'
layout = [[sg.Text(size=(40,1), key='display')]] +\
         [[sg.Button(c, size=(5,-1)) for c in keys[i:i+4]] for i in range(0,20,4)]

window = sg.Window("sgcalc", layout)

while 1:
    event, values = window.read()
    bt_press(event)
    if event == sg.WINDOW_CLOSED or event == 'Quit':
        break

window.close()

Last of all, using wxPython, it came to 20 lines.

import wx

def pack(items, orient=wx.HORIZONTAL):
    sizer = wx.BoxSizer(orient)
    for item in items:
        sizer.Add(item, 1, wx.EXPAND|wx.ALL, 0)
    return sizer

def bt_press(key):
    if   key == 'C': disp.Value = ''
    elif key == '<': disp.Value = disp.Value[:-1]
    elif key == '=': disp.Value = str(round(eval(disp.Value),16))
    else           : disp.Value += key

app = wx.App()
self = wx.Frame(None, title="wxcalc")

keys = '()C<789/456*123-.0=+'
btns = [[wx.Button(self, label=c) for c in keys[i:i+4]] for i in range(0,20,4)]
disp = wx.TextCtrl(self)

self.Bind(wx.EVT_BUTTON, lambda v: bt_press(v.EventObject.Label))
self.SetSizer(pack([disp] + [pack(x) for x in btns], orient=wx.VERTICAL))
self.Show()
app.MainLoop()

The best I can do by for now…:neutral_face:

wxcalc

some friendly comments: ignoring PEP8 always helps in line reduction :slightly_smiling_face:; but eval not only raises an audit event, it also helps causing an exception: #*6=

Thank you for your comment @da-dada,

Some have pointed out the problem of eval. This possibly runs evil code.

Well, there is no problem with eval in Python whatsoever, it’s just the security risc which may turn nasty (you could use ‘style=wx.TE_READONLY’ to level up)
but, ignoring the line count if possible, a much more interesting feature would be to be able to use keys or mouse independent of focus (normal for a usable calculator) :grin:

Thank you for your advice!
I upgraded this within 20 lines (I had to ignored PEP8:multiple imports on one line).

import wx, wx.py.shell

def pack(items, orient=wx.HORIZONTAL):
    sizer = wx.BoxSizer(orient)
    for item in items:
        sizer.Add(item, 1, wx.EXPAND|wx.ALL, 0)
    return sizer

def bt_press(key):
    if   key == 'C': disp.clearCommand()
    elif key == '<': disp.DeleteBack()
    elif key == '=': disp.Execute(disp.getCommand())
    else           : disp.write(key)

app = wx.App()
self = wx.Frame(None, title="wxcalc ver.2")

keys = '()C<789/456*123-.0=+'
btns = [[wx.Button(self, label=c) for c in keys[i:i+4]] for i in range(0,20,4)]
disp = wx.py.shell.Shell(self)

self.Bind(wx.EVT_BUTTON, lambda v: bt_press(v.EventObject.Label) or disp.SetFocus())
self.SetSizer(pack([disp] + [pack(x) for x in btns], orient=wx.VERTICAL))
self.Show()
app.MainLoop()

It’s more wxPythonic isn’t it? :smiley:

wxcalc_ver2

- the ‘upgrade’ seems functionally ok
- it’s definitely more wxPythonic :rofl:
- but the average accountant would jump backwards into your face (or quit the job & has fun with wxPython)

Hey this awesome, I love me some code golf

I went nuts with my in-lining and moved some logic into a dict. Got it down to 14 lines . Pretty darn ugly though. :stuck_out_tongue_closed_eyes:

import wx

def pack(items, orient=wx.HORIZONTAL):
    sizer = wx.BoxSizer(orient)
    sizer.AddMany([(i, 1, wx.EXPAND | wx.ALL, 0) for i in items])
    return sizer

def bt_press(key):
    disp.Value = {'C': lambda v: '', '<': lambda v: v[:-1], '=': lambda v: str(round(eval(v),16))}[key](disp.Value) if key in ['C','=','<'] else (disp.Value + key)

app = wx.App()
self = wx.Frame(None, title="wxcalc")
disp = wx.TextCtrl(self)

self.Bind(wx.EVT_BUTTON, lambda v: bt_press(v.EventObject.Label))
self.SetSizer(pack([disp] + [pack(x) for x in [[wx.Button(self, label=c) for c in '()C<789/456*123-.0=+'[i:i+4]] for i in range(0,20,4)]], orient=wx.VERTICAL))
self.Show()
app.MainLoop()

I’m afraid this piece of art has the same drawbacks as I mentioned at my first reply, but being a sport here the 13 liner (and that need not be the end) :rofl:

import wx

def pack(items, orient=wx.HORIZONTAL):
    sizer = wx.BoxSizer(orient)
    sizer.AddMany([(i, 1, wx.EXPAND | wx.ALL, 0) for i in items])
    return sizer

def bt_press(key):
    disp.Value = {'C': lambda v: '', '<': lambda v: v[:-1], '=': lambda v: str(round(eval(v),16))}[key](disp.Value) if key in ['C','=','<'] else (disp.Value + key)

app, self = wx.App(), wx.Frame(None, title="wxcalc")
disp = wx.TextCtrl(self)

self.Bind(wx.EVT_BUTTON, lambda v: bt_press(v.EventObject.Label))
self.SetSizer(pack([disp] + [pack(x) for x in [[wx.Button(self, label=c) for c in '()C<789/456*123-.0=+'[i:i+4]] for i in range(0,20,4)]], orient=wx.VERTICAL))
self.Show()
app.MainLoop()

I think we’re just about up against the limit here. I got it down to an ugly ugly 8.

You can use a GridBagSizer and then you only need a single sizer, that trick isn’t so bad.
But then I found out disp has a function SetValue, which means you can call it from within the lambda. And so that can all go on one line too.

import wx

app, frame, sizer = wx.App(), wx.Frame(None, title="8 Line Calculator"), wx.GridBagSizer(2,2)
disp = wx.TextCtrl(frame)

frame.Bind(wx.EVT_BUTTON, lambda v: disp.SetValue({'C': lambda v: '', '<': lambda v: v[:-1], '=': lambda v: str(round(eval(v),16))}[v.EventObject.Label](disp.Value) if v.EventObject.Label in ['C', '=', '<'] else (disp.Value + v.EventObject.Label)))
sizer.AddMany([(disp, wx.GBPosition(0,0), wx.GBSpan(1,4), wx.ALL | wx.EXPAND)] + [(wx.Button(frame, label=c), wx.GBPosition(i//4+1,i%4), wx.GBSpan(1, 1)) for i,c in enumerate('()C<789/456*123-.0=+')]) # Add buttons

frame.SetSizer(sizer)
frame.Show()
app.MainLoop()

Python can be hilarious sometimes

Hey, don’t give up, coopsmoss!

I recommend you this post. I love his crazy one line program. :wink:
I decomposed it into multi-lines, but it’s essentially one line.

((tk := __import__("tkinter")),
 (update := lambda t: d.config(text=t)),
 (bt_draw := lambda k,c,l: tk.Button(w, text=k, command=lambda: bt_press(k), width=6).grid(column=c, row=l)),
 (bt_press := lambda k: update("") if k == "C" else
                        update(d["text"][:-1]) if k == "<" else
                        update(str(round(eval(d["text"]), 6))) if k == "=" else
                        update(d["text"] + k)),
 (w := tk.Tk()),
 (w.title("TKalc")),
 (d := tk.Label(w, text="")),
 (d.grid(column=0, row=0, columnspan=5)),
 [bt_draw(k, n%4 + 1, n//4 + 1) for n, k in enumerate("()C<789/456*123-.0=+")],
w.mainloop())

Tk/PY38 only.

1 Like