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

Jak pozbyć się zmiennej statycznej?

0 głosów
247 wizyt
pytanie zadane 12 października w Python przez whiteman808 Mądrala (5,430 p.)
edycja 12 października przez whiteman808

Wie ktoś jak pozbyć zmiennej statycznej Menu.nesting_level i przekazywać zaktualizowane informacje dotyczące poziomu zagnieżdżenia Menu dopiero w momencie wywoływania metody menu.add_item np menu.add_item('Podmenu', submenu.loop) lub menu.add_submenu? Nie wszystkie obiekty Menu mają być podmenu powiązanymi z innymi menu w programie.

from __future__ import annotations

import gettext
import locale
import sys
from dataclasses import dataclass
from typing import Callable, Optional, TextIO

from .user_input import print_input_error, redirect_stdio, wait_for_enter

# mypy and pylint don't work well with gettext.install
lang, _enc = locale.getlocale()
lang = lang or 'en'
_ = gettext.translation('python_misc',
                        localedir='locale',
                        languages=[lang, lang.split('_')[0], 'en'],
                        fallback=True).gettext


@dataclass(frozen=True)
class MenuItem:
    name: str
    action: Callable[[], None]
    key: Optional[str] = None
    visible: bool = True
    enabled: bool = True

    def __str__(self):
        return self.name


class Menu:
    nesting_level = -1

    def __init__(self,
                 title: str,
                 items: list[MenuItem], *,
                 quit_key: Optional[str] = 'q',
                 stdin: TextIO = sys.stdin,
                 stdout: TextIO = sys.stdout):
        self.title = title
        self.items = list(items)
        quit_item = MenuItem('Quit', self._quit, quit_key)
        self._check_for_duplicates(quit_item)
        self.items.append(quit_item)
        self.active = False
        self.quit_key = quit_key
        self.stdin = stdin
        self.stdout = stdout

    @property
    def visible_items(self):
        return [item for item in self.items if item.visible]

    def add_item(self,
                 name: str,
                 action: Callable[[], None],
                 key: Optional[str] = None) -> None:
        item = MenuItem(name, action, key)
        self._check_for_duplicates(item)
        self.items.insert(-1, item)

    def remove_item(self, name: str) -> bool:
        for idx, item in enumerate(self.items):
            if item.name == name:
                del self.items[idx]
                return True
        return False

    def add_submenu(self,
                    name: str,
                    menu: Menu,
                    key: Optional[str] = None) -> None:
        item = MenuItem(name, menu.loop, key)
        self._check_for_duplicates(item)
        self.items.insert(-1, item)

    def loop(self) -> None:
        with redirect_stdio(self.stdin, self.stdout):
            Menu.nesting_level += 1
            if Menu.nesting_level > 0:
                quit_item_title = _('Back')
            else:
                quit_item_title = _('Quit')
            quit_item = MenuItem(quit_item_title, self._quit, self.quit_key)
            self.items[-1] = quit_item
            self.active = True
            while self.active:
                user_choice = self._read_choice()
                if user_choice.action == self._quit:  # pylint: disable=W0143
                    self._quit()
                else:
                    try:
                        user_choice.action()
                        wait_for_enter()
                    except (EOFError, KeyboardInterrupt):
                        pass

    def _check_for_duplicates(self, candidate: MenuItem) -> None:
        for item in self.items:
            if (candidate.key is not None and item.key is not None
                    and candidate.key.lower() == item.key.lower()):
                raise ValueError('duplicate key')

    def _print_title(self) -> None:
        longest_item = max(len(item.name) for item in self.visible_items)
        has_key = any(item.key is not None for item in self.visible_items)
        if has_key:
            longest_key = max(len(item.key) for item in self.visible_items
                              if item.key is not None)
        else:
            longest_key = 0
        field_width = max(len(self.title), longest_item) + 2
        if longest_key > 0:
            field_width += longest_key
        line = '-' * (field_width + 2)
        print(line, file=self.stdout)
        print(f'|{self.title:^{field_width}}|', file=self.stdout)
        print(line, file=self.stdout)

    def _display_menu(self) -> None:
        self._print_title()
        longest = max(len(item.name) for item in self.visible_items)
        for idx, item in enumerate(self.visible_items, start=1):
            label = f'{idx}) {item.name:<{longest}}'
            label += f' [{item.key}]' if item.key else ''
            print(label)

    def _read_choice(self) -> MenuItem:
        with redirect_stdio(self.stdin, self.stdout):
            while True:
                self._display_menu()
                try:
                    user_input = input(_('Your choice: ')).strip().lower()
                except (EOFError, KeyboardInterrupt):
                    print(_('\nExiting...'))
                    return self.items[-1]

                try:
                    if user_input.isdigit():
                        idx = int(user_input)
                        if not 1 <= idx <= len(self.visible_items):
                            raise ValueError
                        candidate = self.visible_items[idx - 1]
                    else:
                        for idx, item in enumerate(self.visible_items):
                            if user_input == item.key:
                                candidate = self.visible_items[idx]
                                break
                        else:
                            raise ValueError
                    if not candidate.enabled:
                        print_input_error(_('invalid operation'))
                        wait_for_enter()
                    else:
                        return candidate
                except ValueError:
                    print_input_error(_('invalid choice'))

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

1 odpowiedź

0 głosów
odpowiedź 13 października przez marcin99b Szeryf (85,330 p.)

Najlepiej to zacznij od refactoringu i nauczenia sie podstawowych dobrych praktyk, typu SOLID

Wie ktoś jak pozbyć zmiennej statycznej Menu.nesting_level i przekazywać zaktualizowane informacje dotyczące poziomu zagnieżdżenia Menu dopiero w momencie wywoływania metody menu.add_item np menu.add_item('Podmenu', submenu.loop) lub menu.add_submenu? Nie wszystkie obiekty Menu mają być podmenu powiązanymi z innymi menu w programie.

Tekst brzmi na coś bardzo skomplikowanego, ale z tego co rozumiem, masz problem z przekazywaniem wartości zmiennej do innych obiektów, bo gubisz się w tym kodzie, bo wszystko robi wszystko, nie wiadomo co ma służyć do czego

No i jeszcze te zagnieżdżenia...

    def _read_choice(self) -> MenuItem:
        with redirect_stdio(self.stdin, self.stdout):
            while True:
                self._display_menu()
                try:
                    user_input = input(_('Your choice: ')).strip().lower()
                except (EOFError, KeyboardInterrupt):
                    print(_('\nExiting...'))
                    return self.items[-1]
 
                try:
                    if user_input.isdigit():
                        idx = int(user_input)
                        if not 1 <= idx <= len(self.visible_items):
                            raise ValueError
                        candidate = self.visible_items[idx - 1]
                    else:
                        for idx, item in enumerate(self.visible_items):
                            if user_input == item.key:
                                candidate = self.visible_items[idx]
                                break
                        else:
                            raise ValueError
                    if not candidate.enabled:
                        print_input_error(_('invalid operation'))
                        wait_for_enter()
                    else:
                        return candidate
                except ValueError:
                    print_input_error(_('invalid choice'))

Wrócisz do kodu po miesiącu i zrozumienie co jak działa zajmie ci 10 razy więcej czasu niż powinno (ja czytając na szybko nie rozumiem, musiałbym się skupić i dokładnie przeanalizować)

komentarz 14 października przez whiteman808 Mądrala (5,430 p.)
komentarz 14 października przez marcin99b Szeryf (85,330 p.)
Dlaczego nie wrzuciłeś tego na githuba?
komentarz 14 października przez marcin99b Szeryf (85,330 p.)

A czy w dobrym kierunku... no według mnie to jest źle zaprojektowane na etapie podstaw, poczytaj sobie o dobrych praktykach, ale takich podstawowych. W poprzednim pytaniu (https://forum.pasja-informatyki.pl/599155/wybor-jezyka-pod-web-dev-ruby-czy-python) przejmowałeś sie rzeczami typu free threading, wydajność LSP, filozofie wokół narzędzi, mówiłeś że używasz neovima, a w praktyce widać że masz braki w podstawach typu zasady SOLID.

Tych zasad w sumie jest dużo, bo masz jakieś wzorce projektowe, jest separation of concerns, YAGNI, DRY, KISS, masz tematy typu coupling and cohesion i mnóstwo innych, ale większość bazuje na SOLID (dużo ludzi ucząc się o SOLID, nie rozumie w pełni co autor miał na myśli, a te inne zasady dodają troche kontekstu, więc warto je znać).

Tak na szybko

_read_choice

  • Wyświetla menu
  • Czyta input
  • Parsuje input
  • Waliduje
  • Obsługuje błędy

I nazwa jest myląca bo sugeruje sam odczyt decyzji

Menu miesza:

  • Wyświetlanie (UI)
  • Logikę biznesową (walidacja)
  • Zarządzanie stanem (active, nesting_level)
  • Input handling
def make_binop(binary_op: Callable[[Any, Any], Any]

czemu Any zamiast typów?

No i te wielkie zagnieżdżenia w _read_choice, które w dodatku nie mają kodu, który sie samodokumentuje - jest logika którą trzeba analizować żeby odkryć co się tam dzieje

Jest dużo błędów typu brakująca walidacja albo zbyt mała obsługa błędów, ale głównym problemem jest brak planowania. Nie jest skrajnie źle, bo mimo wszystko jest jakiś podział na metody i klasy, ale ten podział nie jest dobrze zaplanowany, on po prostu istnieje, a rzeczywisty kod sprawia wrażenie że wszystko robi wszystko

komentarz 14 października przez marcin99b Szeryf (85,330 p.)

Może podczas nauki, za bardzo skupiałeś się na nauce elementów języka, a pomijałeś nauke programowania samego w sobie. Troche tak jak ludzie którzy uczą się jakiegoś języka ludzkiego, znają dużo słówek, znają zasady gramatyczne, ale jak mają spędzić dzień w innym kraju to mają problemy z podstawowym dogadaniem sie.

No na pewno pomocne będzie rzeczywiste pisanie kodu, niezależnie w jakim języku, bo jak sam zauważyłeś, pewne problemy są zauważalne jak kod ma pewien rozmiar. Nie wiem ile linii ma teraz całość, obstawiam że kilkaset i już masz problemy których nie powinieneś spotkać... ale pouczysz się, poprawisz dużo rzeczy, kod będzie dużo bardziej zadbany... później znowu trafisz na podobną sytuacje, ale tym razem przy 2 tysiącach linii, więc znowu się nauczysz na błędach, znowu poprawisz, znowu będzie ci się wydawało że już jest super... a później przy 10 tysiącach znowu wyjdzie że jednak jest źle, znowu poprawisz, znowu się nauczysz... później zaczniesz pracować gdzieś gdzie projekt ma kilkaset tysięcy linii i odkryjesz że to co ty robiłeś w domu, było słabo zaplanowane... później odkryjesz że ten projekt jest słabo zaprojektowany i wszystko dałoby się zrobić lepiej... i tak dalej, w nieskończoność

Możesz poczytać albo pooglądać jakieś rzeczy o tym jak powinno się projektować kod, przykładowo dzielenie na warstwy, może jakaś czysta architektura, może techniczna część DDD. Możesz przejrzeć dokładnie https://refactoring.guru/design-patterns

Tylko że tych rzeczy nie zrozumiesz, jak nie będą dotyczyć sytuacji, na które sam trafiasz. Będzie ci się wydawało że wiesz o co chodzi, ale będziesz umiał tylko teoretyczne definicje, zamiast realnej wiedzy "czego kiedy użyć i dlaczego". Musisz rozwiązywać swoje problemy, żeby uczyć się które rozwiązania do czego pasują a do czego nie.

W tamtym pytaniu podkreślałem że wybór języka nie ma dużego znaczenia, bo język można łatwo zmienić i dużo ludzi ostatecznie zna kilka języków, a 90% wiedzy to właśnie "jak programować".

komentarz 15 października przez whiteman808 Mądrala (5,430 p.)
No dobrze, to proszę o zaproponowanie mi konkretnej ścieżki nauki co i jak, w tym jak najlepiej zrozumieć w praktyce na czym polega SOLID. Co do samego czytania o dobrych praktykach to obawiam się że bez pisania samodzielnie kodu w praktyce nie wyniknie nic dobrego.

Mój cel to ogarnięcie jak pisać aplikacje webowe z użyciem frameworka Django. Chcę popisać sobie rzeczy typu calc.py żeby nabrać wpierw wprawy w posługiwaniu się Pythonem zanim zabiorę się za używanie gotowych frameworków.

Wolę podpytać kogoś mądrzejszego od siebie bo trochę zaczynam gubić się w trakcie tej nauki i potrzebuję porady jak zdobyć te 90% wiedzy jak programować.
komentarz 15 października przez marcin99b Szeryf (85,330 p.)
Przecież napisałem... i to wiele razy
komentarz 15 października przez whiteman808 Mądrala (5,430 p.)
Nie no, chciałem w powyższym komentarzu tylko upewnić się czy nie pomijam jakiejś fundamentalnej wiedzy przez co samodzielne pisanie kodu może nie wystarczyć. Na początku trochę tak odebrałem powyższy komentarz dlatego dopytałem.

Ale z tego co poczytałem ponownie co pisałeś to chyba ci chodzi o to, że w trakcie nauki za bardzo skupiałem się na elementach języka a za mało samodzielnie pisałem kod. Czyli o ile dobrze rozumiem im więcej będę robił projektów typu calc.py, networking.py, todo_list.py i temu podobne tym lepiej?
komentarz 15 października przez whiteman808 Mądrala (5,430 p.)
Jeśli to drugie to tym bardziej podoba mi się ta cała nauka programowania i myślę że dobrze zrobiłem że wybrałem Pythona a nie np. C czy C++. W Pythonie łatwiej i szybciej zrobić to co przychodzi mi do głowy, nie trzeba jak w C męczyć się z pierdołami tylko wycieki pamięci.

Wiesz to są dopiero moje pierwsze projekty więc na początku to chyba normalne że kod może nie być najlepszej jakości, przynajmniej dopóki nie nabiorę wprawy.

Ostatnio robię tak że sobie czasem włączam muzykę w tle czy jakiś serial do kodowania w Pythonie.
komentarz 15 października przez marcin99b Szeryf (85,330 p.)

Czyli o ile dobrze rozumiem im więcej będę robił projektów typu calc.py, networking.py, todo_list.py i temu podobne tym lepiej?

Tak, ale musisz to po prostu zbalansować

  • Sam kod = będziesz powtarzał błędy i zastanawiał sie czemu coś nie działa (będziesz walczyć z problemami których nawet nie powinieneś mieć)
  • Samo czytanie = nie zrozumiesz i zapomnisz
1
komentarz 15 października przez marcin99b Szeryf (85,330 p.)

Jeśli to drugie to tym bardziej podoba mi się ta cała nauka programowania i myślę że dobrze zrobiłem że wybrałem Pythona a nie np. C czy C++. W Pythonie łatwiej i szybciej zrobić to co przychodzi mi do głowy, nie trzeba jak w C męczyć się z pierdołami tylko wycieki pamięci.

Pamiętaj że nie musisz wybierać skrajności. Większość rynku to języki pomiędzy nimi.

Ja nie lubie ani pythona ani c++, bo to skrajności, po przeciwnych stronach, ale obie są dla mnie niewygodne.

Podobne pytania

+5 głosów
3 odpowiedzi 3,211 wizyt
0 głosów
4 odpowiedzi 5,090 wizyt

93,605 zapytań

142,529 odpowiedzi

322,999 komentarzy

63,096 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
...