Menu w Pythonie - użyć OrderedDict czy zwykłej listy zawierającej instancje MenuItem?

0

Witam, piszę sobie klasę do obsługi menu w konsoli

from collections import OrderedDict
 
 
def read_int(prompt='> ', errmsg='invalid integer'):
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print(f'Error: {errmsg}')
 
 
class Menu:
    def __init__(self, title, description='', fancy_banner=True, fill_char='#', prompt='Your choice: '):
        self.title = title
        self.description = description
        self.items = OrderedDict()
        self.fancy_banner = fancy_banner
        self.fill_char = fill_char
        self.active = True
        self.prompt = prompt
 
    def quit(self):
        self.active = False
 
    def add_item(self, title, action):
        if title in self.items:
            raise ValueError(f"cannot add item, duplicate item '{self.title}'")
        self.items[title] = action
 
    def remove_item(self, title):
        if title not in self.items:
            raise ValueError(f"cannot remove item, item '{self.title}' not found in menu '{self.title}'")
        self.items.pop(title)
 
    def invoke(self, title, *args, **kwargs):
        if title not in self.items:
            raise ValueError(f"cannot invoke function, item '{self.title}' not found in menu '{self.title}'")
        self.items[title](*args, **kwargs)
 
    def print_banner(self):
        if self.fancy_banner:
            banner = self.fill_char * (len(self.title) + 4) + '\n'
            banner += f'# {self.title:^{len(self.title)}s} #\n'
            banner += self.fill_char * (len(self.title) + 4) + '\n'
        else:
            banner = self.title + '\n'
        return banner
 
    def loop(self):
        while self.active:
            for offset, item in enumerate(self.items):
                print(f'{offset}) {item}\n')
                try:
                    choice = int(input(self.prompt))
                    if choice not in enumerate(self.items):
                        raise ValueError()
                except ValueError:
                    print('Error: invalid choice!')
                self.invoke(item)
 
    def __str__(self):
        return self.title
 
    def __repr__(self):
        return f"Menu(title='{self.title}')"
 
    def __contains__(self, item):
        return item in self.items
 
 
def main():
    main_menu = Menu(title='Main menu')
    main_menu.loop()
 
 
if __name__ == '__main__':
    main()

Czy dobrze robię używając OrderedDict do przechowywania itemów czy lepiej stworzyć osobną klasę MenuItem i przechowywać w liście instancje tej klasy? Chcę mieć dostęp do elementów menu zarówno po wartościach przesunięcia jak w normalnej liscie, jak i po tytułach opcji menu.

0

Trochę tutaj cudujesz. Szczególnie ta metoda loop.

Co do OrderedDict to od 3.7 zwykły dict ma ordering w standardzie jeśli ma to dla Ciebie znaczenie.

0

Użyłbyś zwykłej listy zawierającej elementy typu MenuItem?

chcesz tworzyć wiele instancji Menu ?

Tak.

0

Stwórz sobie po prostu oddzielne klasy dla tych typów submenu i wywołuj je z Menu. Tak jak mówiłem, Menu powinno być orchestratorem.

0

Klasy submenu mogą dziedziczyć po Menu? To był by dramat. Poczytaj o kompozycji i ogólnie zasadach OOP, bo tutaj jest pies pogrzebany.

0

Dobrze kombinuję?

class MenuItem:
    def __init__(self, name, action):
        self.name = name
        self.action = action

    def invoke(self):
        return self.action()

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"MenuItem(name='{self.name}', action={self.action})"


class Menu:
    def __init__(self, title, description='', fancy_banner=True, fill_char='#', prompt='Your choice: '):
        self.title = title
        self.description = description
        self.items = [MenuItem(name='Quit', action=self.quit)]
        self.fancy_banner = fancy_banner
        self.fill_char = fill_char
        self.active = True
        self.prompt = prompt

    def quit(self):
        self.active = False

    def add_item(self, item):
        self.items.insert(-1, item)

    def remove_item(self, item):
        self.items.remove(item)

    @property
    def banner(self):
        if self.fancy_banner:
            max_length = max(len(item.name) for item in self.items) + 3
            banner_size = max(len(self.title) + 4, max_length)
            banner = self.fill_char * banner_size + '\n'
            banner += f'# {self.title:^{banner_size - 4}s} #\n'
            banner += self.fill_char * banner_size
        else:
            banner = self.title
        return banner

    def loop(self):
        while self.active:
            print(self.banner)
            for offset, item in enumerate(self.items):
                print(f'{offset}) {item}')
            choice = None
            while choice is None:
                try:
                    choice = int(input(self.prompt))
                    if choice < 0 or choice >= len(self.items):
                        raise ValueError()
                except ValueError:
                    print('Error: invalid choice!')
            self.items[choice].invoke()

    def __str__(self):
        return self.title

    def __repr__(self):
        return f"Menu(title='{self.title}')"

    def __contains__(self, item):
        return item in self.items


def main():
    main_menu = Menu(title='Main menu')
    main_menu.loop()


if __name__ == '__main__':
    main()
0

Obczaj sobie moduł cmd z biblioteki standardowej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1