• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Kalkulator w Pythonie + biblioteka menu v1

0 głosów
87 wizyt
pytanie zadane 11 października w Nasze projekty przez whiteman808 Mądrala (5,430 p.)

Hej, napisałem pierwszą wersję kalkulatora wykorzystujący samodzielnie zrobiony przeze mnie interfejs menu.

Co można poprawić w tym kodzie? Jakie bugi przeoczyłem? Czy dobrze robię robiąc z MenuItem data klasę czy lepiej przekazywać do konstruktora Menu słownik?

Kod:

~/python_misc ❯ cat calc.py                                                                                                                           21:03:15
#!/usr/bin/env python3
from ui.menu import Menu, MenuItem
from ui.user_input import read_number


def add() -> None:
    first = read_number('Enter the first operand: ')
    second = read_number('Enter the second operand: ')
    print(f'The result is {first + second}.')


def subtract() -> None:
    first = read_number('Enter the first operand: ')
    second = read_number('Enter the second operand: ')
    print(f'The result is {first - second}.')


def multiply() -> None:
    first = read_number('Enter the first operand: ')
    second = read_number('Enter the second operand: ')
    print(f'The result is {first * second}.')


def divide() -> None:
    first = read_number('Enter the first operand: ')
    second = read_number('Enter the second operand (cannot be zero): ',
                         nonzero=True)
    print(f'The result is {first / second}.')


items = [MenuItem('Add', add),
         MenuItem('Subtract', subtract),
         MenuItem('Multiply', multiply),
         MenuItem('Divide', divide)]
menu = Menu('Calculator', items)
menu.loop()
~/python_misc ❯ cat ui/menu.py                                                                                                                        21:03:20
from dataclasses import dataclass
from typing import Callable


@dataclass
class MenuItem:
    name: str
    action: Callable[[], None]

    def __str__(self):
        return self.name


class Menu:
    nesting_level = -1
    _quit_choice = -1

    def __init__(self, title: str, items: list[MenuItem]):
        self.title = title
        self.items = items
        self.active = False

    def _print_title(self) -> None:
        print('-' * (len(self.title) + 4))
        print(f'| {self.title} |')
        print('-' * (len(self.title) + 4))

    def _read_choice(self) -> int:
        while True:
            self._print_title()
            for idx, item in enumerate(self.items):
                print(f'{idx + 1}) {item}')
            print('q)', 'Back' if Menu.nesting_level > 0 else 'Quit')
            user_input: int | str = input('Your choice: ').lower()
            if user_input == 'q':
                return Menu._quit_choice
            try:
                user_input = int(user_input)
                if user_input not in range(1, len(self.items) + 1):
                    raise ValueError()
                return user_input - 1
            except ValueError:
                print('Error: invalid choice. Try again...')

    def _quit(self) -> None:
        self.active = False
        Menu.nesting_level -= 1

    def add_item(self, name, action):
        self.items.append(MenuItem(name, action))

    def remove_item(self, name):
        for idx, item in enumerate(self.items):
            if item.name == name:
                del self.items[idx]

    def loop(self) -> None:
        Menu.nesting_level += 1
        self.active = True
        while self.active:
            user_choice: int = self._read_choice()
            if user_choice == self._quit_choice:
                self._quit()
            else:
                self.items[user_choice].action()
                input('\nPress any key and type ENTER...\n')
~/python_misc ❯ cat ui/user_input.py                                                                                                                  21:03:26
from typing import Any, Optional, overload


def get_default_prompt(lower: bool, upper: bool, nonzero: bool):
    prompt = 'Enter the number'
    if lower or upper or nonzero:
        prompt += ' ('
    if lower:
        prompt += f'starting from {lower}'
    if upper:
        prompt += f'up to {upper}'
    if nonzero:
        if lower or upper:
            prompt += ', '
        prompt += 'cannot be zero'
    if lower or upper or nonzero:
        prompt += ')'
    prompt += ': '
    return prompt


@overload
def read_number[T](prompt: str = ..., *,
                   lower: Optional[T] = ...,
                   upper: Optional[T] = ...,
                   nonzero: bool = ...,
                   _type: type[T]) -> T: ...


@overload
def read_number[T](prompt: str = ..., *,
                   lower: Optional[T] = ...,
                   upper: Optional[T] = ...,
                   nonzero: bool = ...) -> float: ...


def read_number[T](prompt: str = '', *,
                   lower: Optional[T] = None,
                   upper: Optional[T] = None,
                   nonzero: bool = False,
                   _type: type = float):
    if not prompt:
        prompt = get_default_prompt(bool(lower),
                                    bool(upper),
                                    bool(nonzero))
    while True:
        try:
            try:
                number: Any = _type(input(prompt))
            except ValueError as exc:
                raise ValueError('not a number') from exc
            if nonzero and number == 0:
                raise ValueError('number is equal to zero')
            if lower and number < lower:
                raise ValueError('number too small')
            if upper and number > upper:
                raise ValueError('number too big')
            return number
        except ValueError as exc:
            print(f'Error: {exc}. Try again...')

Zaloguj lub zarejestruj się, aby odpowiedzieć na to pytanie.

Podobne pytania

0 głosów
0 odpowiedzi 80 wizyt
+2 głosów
2 odpowiedzi 644 wizyt
+1 głos
3 odpowiedzi 1,750 wizyt

93,604 zapytań

142,526 odpowiedzi

322,991 komentarzy

63,087 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Kursy INF.02 i INF.03
...