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()