I've written a simple calculator in Python. I want to show not much effect of the action but the logic behind it, by that I mean console menu implementation in the Menu
class. I'm curious about what I can improve in the Menu
class, what do you think about it and other helper functions? I'd like to hear also what useful functionality can I add to the Menu
library.
Here's code with an example menu implementation in the calculator:
# -*- coding: utf-8 -*-
import enum
from collections import OrderedDict # I used OrderedDict due to the move_to_end function
from dataclasses import dataclass
from typing import Any
@enum.unique
class BannerStyle(enum.IntEnum):
FRAME_BOX = enum.auto()
HASH = enum.auto()
def print_banner(title, style=BannerStyle.FRAME_BOX):
match style:
case BannerStyle.FRAME_BOX:
print('-' * (len(title) + 4))
print(f'| {title:^} |')
print('-' * (len(title) + 4))
case BannerStyle.HASH:
print('#' * (len(title) + 4))
print(f'# {title:^} #')
print('#' * (len(title) + 4))
def read_int(prompt='Enter an integer: ', errmsg='Not an integer.', default=None):
while True:
try:
user_input = input(prompt)
if default and not user_input:
user_input = default
return int(user_input)
except ValueError:
print(errmsg)
def read_float(prompt='Enter an floating point number: ', errmsg='Not an floating point number.', default=None):
while True:
try:
user_input = input(prompt)
if default and not user_input:
user_input = default
return float(user_input)
except ValueError:
print(errmsg)
def print_list(lst):
for idx, item in enumerate(lst):
print(f'{idx}) {item}')
@dataclass
class UserChoice:
idx: int
choice: Any
def read_choice(choices, prompt='Your choice (enter an integer): ', errmsg='Invalid choice.', default=None):
if default and default not in choices:
raise ValueError(f'default value {default} is not present in choices argument')
while True:
try:
print('Available choices:')
print_list(choices)
user_input = input(prompt)
if default and not user_input:
user_input = default
user_input = int(user_input)
if not 0 <= user_input < len(choices):
raise ValueError
return UserChoice(idx=user_input, choice=choices[user_input])
except (ValueError, IndexError):
print(errmsg)
class Menu:
def __init__(self, title, banner_style=BannerStyle.FRAME_BOX):
self.title = title
self.active = True
self.items = OrderedDict({'Quit': self.quit})
self.banner_style = banner_style
def add_item(self, title, func):
self.items[title] = func
self.items.move_to_end('Quit')
def remove_item(self, title):
del self.items[title]
def add_submenu(self, title, submenu):
self.items[title] = submenu.quit
@property
def _items_titles(self):
return list(self.items.keys())
def invoke(self, title):
return self.items[title]()
def quit(self):
self.active = False
def loop(self):
while self.active:
print_banner(self.title)
user_input = read_choice(self._items_titles).idx
self.invoke(self._items_titles[user_input])
def __repr__(self):
return f"MenuItem(title='{self.title}', active={self.active})"
def __str__(self):
return self.title
class CalculatorUI:
def add(self):
self._read_numbers()
print(f'The result is {self.a + self.b}')
def subtract(self):
self._read_numbers()
print(f'The result is {self.a - self.b}')
def multiply(self):
self._read_numbers()
print(f'The result is {self.a * self.b}')
def divide(self):
self._read_numbers_div()
print(f'The result is {self.a / self.b}')
def modulo(self):
self._read_numbers_div()
print(f'The result is {self.a % self.b}')
def _read_numbers(self):
self.a = read_float('Enter first number: ')
self.b = read_float('Enter second number: ')
def _read_numbers_div(self):
self.a = read_float('Enter first number: ')
self.b = read_float('Enter second number: ')
while self.b == 0:
print('Number cannot be zero.')
self.b = read_float('Enter second number: ')
calc = CalculatorUI()
def test_menu():
menu = Menu(title='Welcome to calculator')
menu.add_item('Add', calc.add)
menu.add_item('Subtract', calc.subtract)
menu.add_item('Multiply', calc.multiply)
menu.add_item('Divide', calc.divide)
menu.add_item('Modulo', calc.modulo)
menu.loop()
if __name__ == '__main__':
test_menu()