- Docstrings: You should include a
docstring
at the beginning of every method/class/module you write. This will help any documentation identify what your code is supposed to do. - Naming: Python variables and parameters should be in
snake_case
, notcamelCase
. len
as condition: You shouldn't useif len(self.title):
, instead you should check if it doesn't exist,if not self.title
, as it is more compliant with PEP-8. This StackOverflow answer provides more insight.- String formatting: This one is a personal preference. I like to use
f""
to format my strings because it allows me to directly include variables into the string, without having to chain a.format()
onto the end. Again, this is purely personal, but see how you like it. - Imports: Another personal preference, I like to organize my imports alphabetically so it's more organized. This is all up to you if you want to keep with this practice, but do make sure to follow PEP-8 Import Guide to make sure you're following standard and accepted practies.
- Unnecessary methods:
show_title
andshow_header
is basically extra and unneeded code. You're essentially redefining theprint
method. Just print the title/header whenever you need to, instead of calling these methods.
Updated Code
import os
import sys
class Menu:
""" Menu class for displaying and adding to menu """
def __init__(self, *items, **kwargs):
self.items = []
self.title = kwargs.get('title', '')
self.header = kwargs.get('header', '')
for item in items:
self.add(item)
def add(self, *items):
""" Adds each element in `list` to items """
for item in items:
self.items.append(item)
if hasattr(item, 'sub_menu'):
self.add_parent_to_child(item.sub_menu)
def show(self):
""" Displays the items and calls method to collect user input """
clear()
if self.header:
print(self.header)
if self.title:
print(self.title)
for i, item in enumerate(self.items):
print(f"[{i + 1}] {item.title}")
self.choice()
def choice(self):
""" Collects user input """
while 1:
try:
choice = input('> ')
if choice == 'q':
clear()
sys.exit()
if choice == 'b':
if hasattr(self, 'parent'):
self.show()
else:
self.show()
continue
if not choice.isdigit():
raise ValueError
item = self.items[int(choice) - 1]
if hasattr(item, 'callback'):
item.callback()
if hasattr(item, 'sub_menu'):
item.sub_menu.show()
if not hasattr(item, 'sub_menu') and not hasattr(item, 'callback'):
print(f"{item.title} is an empty menu...")
except (ValueError, IndexError):
self.show()
continue
def add_parent_to_child(self, child):
""" Adds the passed `child` to the parent of the object """
child.parent = self
class MenuItem:
""" Specific class for items in the menu """
def __init__(self, title, sub_menu=None, callback=None, tag=''):
self.title = title
if sub_menu is not None:
self.sub_menu = sub_menu
if self.title:
sub_menu.title = self.title
if callback is not None:
self.callback = callback
if tag:
self.tag = tag
def clear():
""" Clears the console screen """
os.system('cls') if os.name == 'nt' else os.system('clear')
if __name__ == '__main__':
ITEM_1 = MenuItem('empty test 1')
ITEM_2 = MenuItem('empty test 2')
SUBMENU_1 = Menu(ITEM_1, ITEM_2)
ITEM_3 = MenuItem('sub menu', sub_menu=SUBMENU_1)
TEST_MENU = Menu(ITEM_1, ITEM_2, ITEM_3, title='Main Menu')
TEST_MENU.show()