Wyrażenie typu bool obliczanie dynamicznie

0

Witam,

Mam takie klasy i chcę aby wyrażenie not subject.calendar.events było obliczanie w trakcie działania programu

# plik history/menu.py
class HistoryQuizMenu(Menu):
    def __init__(self, subject):
        super(HistoryQuizMenu, self).__init__(title='History quiz')
        self.subject = subject
        self.calendar = subject.calendar
        self.failed_when = not subject.calendar.events # <- chodzi mi o to wyrażenie
        self.failed_when_msg = 'event list is empty'
        self.add_item('Ask about events dates', self.ask_about_events_dates)
        self.add_item('Ask about events centuries', self.ask_about_events_centuries)
        self.add_item('Ask about events epochs', self.ask_about_events_epochs)

    def ask_about_events_dates(self):
        take_quiz(self.subject, HistoryTest(HistoricalDateQuestion, self.calendar.events))

    def ask_about_events_centuries(self):
        take_quiz(self.subject, HistoryTest(HistoricalCenturyQuestion, self.calendar.events))

    def ask_about_events_epochs(self):
        take_quiz(self.subject, HistoryTest(HistoricalEpochQuestion, self.calendar.events))

# plik utils/menu.py
import sys


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

    def __str__(self):
        return self.name

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

    def invoke(self):
        self.action()


class Menu:
    def __init__(self, title, fancy_banner=True, banner_fill='#', return_handlers=None, quit_handlers=None,
                 quit_option_name='Quit', failed_when=False, failed_when_msg='conditional failed'):
        self.title = title
        self.fancy_banner = fancy_banner
        self.banner_fill = banner_fill
        self.return_handlers = [] if return_handlers is None else return_handlers
        self.quit_handlers = [] if quit_handlers is None else quit_handlers
        self.active = True
        self.items = []
        self.__quit_option_name = quit_option_name
        self.failed_when = failed_when
        self.failed_when_msg = failed_when_msg
        self.items.append(MenuItem('Quit', self.quit))

    def __print_banner(self, fill):
        if self.fancy_banner:
            min_length = max([len(item.name) for item in self.items]) + 3 + len(str(len(self.items)))
            banner_size = len(self.title) + 4 if len(self.title) + 4 > min_length else min_length
            print(fill * banner_size)
            print(f'{fill} {self.title:^{banner_size - 4}} {fill}')
            print(fill * banner_size)
        else:
            print(self.title)

    def __print_items(self):
        for index, item in enumerate(self.items):
            print(f'[{index}] {item}')

    def __read_choice(self, prompt='Your choice: ', error='Error: invalid choice!'):
        while True:
            try:
                choice = int(input(prompt))
                if choice < 0:
                    raise ValueError(error)
                return self.items[choice]
            except (IndexError, ValueError):
                print(error)
                print('Valid choices are:')
                self.__print_items()

    @property
    def quit_option_name(self):
        return self.__quit_option_name

    @quit_option_name.setter
    def quit_option_name(self, value):
        item = self.items.pop(-1)
        item.name = value
        self.items.append(item)
        self.__quit_option_name = value

    def quit(self):
        self.active = False
        if self.quit_handlers:
            for func in self.quit_handlers:
                try:
                    func()
                except Exception as e:
                    print(f'Exception at the quit handler function {func.__name__}: "{e}".', file=sys.stderr)

    def register_return_handler(self, func):
        self.return_handlers.append(func)

    def unregister_return_handler(self, func):
        self.return_handlers.remove(func)

    def register_quit_handler(self, func):
        self.quit_handlers.append(func)

    def unregister_quit_handler(self, func):
        self.quit_handlers.remove(func)

    def add_item(self, title, action):
        self.items.append(MenuItem(title, action))
        self.items.append(self.items.pop(-2))

    def remove_item(self, index):
        del self.items[index]

    def add_submenu(self, menu, item_name=None):
        if item_name is None:
            item_name = menu.title
        menu.quit_option_name = 'Back'
        self.add_item(item_name, menu.loop)

    def loop(self):
        if self.failed_when:
            print(f'Error: {self.failed_when_msg}.')
            return
        self.active = True
        while self.active:
            self.__print_banner(self.banner_fill)
            self.__print_items()
            choice = self.__read_choice()
            try:
                choice.invoke()
            except Exception as e:
                print(f'Exception at the invoke function: "{e}", menu choice: "{choice.name}".', file=sys.stderr)
            if self.return_handlers:
                for func in self.return_handlers:
                    try:
                        func()
                    except Exception as e:
                        print(f'Exception at the return handler function {func.__name__}: "{e}".', file=sys.stderr)

Mój problem polega na tym, że kiedy dodam nowe wydarzenie i zechcę zrobić quiz to w zmiennej failed_when nadal pozostaje wartość wyrażenia, która została jej przypisana przy inicjalizacji klasy.

Kod:

#!/usr/bin/env python3
import atexit
from subjects.history.base import Calendar, HistorySubject
from ui.utils.menu import Menu
from ui.settings.menu import SettingsMenu
from ui.subjects.history.menu import EpochManagerMenu, EventManagerMenu, HistoryQuizMenu


# TODO: implement update_db method
def update_db():
    pass


def main():
    atexit.register(update_db)
    menu = Menu('Main menu', fancy_banner=True)
    default_calendar = Calendar(name='Calendar')
    history_subject = HistorySubject(calendar=default_calendar)
    menu.add_submenu(HistoryQuizMenu(history_subject))
    menu.add_submenu(EventManagerMenu(history_subject))
    menu.add_submenu(EpochManagerMenu(history_subject))
    menu.add_submenu(SettingsMenu(history_subject))
    menu.loop()
    print('Good bye!')


if __name__ == '__main__':
    main()

Podam może aktualne wejście i wyjście:

#################
#   Main menu   #
#################
[0] History quiz
[1] Manage events
[2] Manage epochs
[3] Settings
[4] Quit
Your choice: 0
Error: event list is empty.
#################
#   Main menu   #
#################
[0] History quiz
[1] Manage events
[2] Manage epochs
[3] Settings
[4] Quit
Your choice: 1
###########################
#      Manage events      #
###########################
[0] Add event
[1] Remove event
[2] Search events at date
[3] Search events at period
[4] Search events at epoch
[5] Display events
[6] Back
Your choice: 0
Enter a year in the format year [BCE|CE]. Part BCE|CE is optional.
If era won't be specified, then default value will be CE.
> 150
Enter an event description: wydarzenie 1
Are you sure you want to add this event?
Your choice [y|n]
> y
Event added.
###########################
#      Manage events      #
###########################
[0] Add event
[1] Remove event
[2] Search events at date
[3] Search events at period
[4] Search events at epoch
[5] Display events
[6] Back
Your choice: 0
Enter a year in the format year [BCE|CE]. Part BCE|CE is optional.
If era won't be specified, then default value will be CE.
> 610
Enter an event description: wydarzenie 2
Are you sure you want to add this event?
Your choice [y|n]
> y
Event added.
###########################
#      Manage events      #
###########################
[0] Add event
[1] Remove event
[2] Search events at date
[3] Search events at period
[4] Search events at epoch
[5] Display events
[6] Back
Your choice: 6
#################
#   Main menu   #
#################
[0] History quiz
[1] Manage events
[2] Manage epochs
[3] Settings
[4] Quit
Your choice: 0 <- tu pojawia się bug w programie
Error: event list is empty.
#################
#   Main menu   #
#################
[0] History quiz
[1] Manage events
[2] Manage epochs
[3] Settings
[4] Quit
Your choice: 4
Good bye!

Gdzie pojawia się problem zaznaczyłem odpowiednio.

0

getter?

    self._failed_when = not subject.calendar.events 

    def get_failed_when(self):
        return not self.subject.calendar.events

0

Jak to zrobić bez dublowania r-wartości zmiennej _failed_when bez osadzania na sztywno w kodzie żądanej wartości? Co jakbym chciał w trakcie działania programu zmienić jej wartość?

2

Ja chyba nie rozumiem pytania za bardzo. Możesz przecież zrobić np.

self.failed_when = lambda : not subject.calendar.events

I teraz wywołanie self.failed_when() zwróci ci dynamicznie aktualną wartość not subject.calendar.events, nic się nie dubluje.

0

@doskanoness: No a czemu nie chcesz zrobić funkcji, tak jak pisał @Arthan ?

0

Dlatego, że w różnych instancjach _failed_when może przyjmować różne wartości.

def get_failed_when(self):
    return not self.subject.calendar.events

Co jeśli chciałbym zmienić wartość _failed_when?

0
doskanoness napisał(a):

Dlatego, że w różnych instancjach _failed_when może przyjmować różne wartości.

def get_failed_when(self):
    return not self.subject.calendar.events

Co jeśli chciałbym zmienić wartość _failed_when?

Mam rozumieć że chcesz zrobić coś takiego?

if coś:
  self.failed_when = lambda: not subject.calendar.events
if coś2:
  self.failed_when = lambda: someOtherValue
if coś3:
  self.failed_when = lambda: someOtherValue2

tak żeby self.failed_when() wołało różne lambdy?

0

Chcę żeby self.failed_when() wywoływało lambdę zapisaną w _failed_when.

Oto co mam na myśli:

    def loop(self):
        if self.failed_when():
            print(f'Error: {self.failed_when_msg}.')
            return
        ...
0
doskanoness napisał(a):

Chcę żeby self.failed_when() wywoływało lambdę zapisaną w _failed_when.

Oto co mam na myśli:

    def loop(self):
        if self.failed_when():
            print(f'Error: {self.failed_when_msg}.')
            return
        ...

No to czemu nie zrobisz z tego funkcji?

def failed_when(self):
    # tutaj kod

def loop(self):
     if self.failed_when():
        print(f'Error: {self.failed_when_msg}.')
        return
0

Jak przekażę wyrażenie zwracające return True if abc else False podczas tworzenia instancji klasy Menu? failed_when może się różnić w zależności od menu. Ja to ogarniam tak w konstruktorze:

        self.failed_when = failed_when
        self.failed_when_msg = failed_when_msg

Przekazuję lambdę w konstruktorze i potem daję wywołanie self.failed_when() w metodzie loop.

0
doskanoness napisał(a):

Przekazuję lambdę w konstruktorze i potem daję wywołanie self.failed_when() w metodzie loop.

Aa, kej. Myślałem że ją inicjalizujesz w środku.

0

Za niedługo wrzucę kolejnego commita to będziesz mógl podejrzeć jak konkretnie zaimplementowałem mechanizm menu.

0

HistoryQuizMenu jest potomkiem Menu, jak dla mnie nadpisujesz funkcję rodzica w potomku i już. Moim zdaniem ładniej i czytelniej niż lambda, zwłaszcza jeśli Ci się gdzieś ta funkcja rozrośnie.


class Menu:
    @property
    def failed_when(self):
        return False  # domyślna wartość, możesz też walić tu errorem jeśli chcesz wymusić nadpisywanie

    def loop(self):
        print(self.failed_when) # sprawdzasz jak zwykłą zmienną, w rodzicu, korzystając z funkcji z klasy dziedziczącej.

class HistoryQuizMenu(Menu):   
    @property
    def failed_when(self):
        return True # tu sobie sprawdzasz co tylko chcesz i jak chcesz i jest to funkcja odpowiedzialna jedynie za to - nie pakujesz wszystko do init
        
menu = HistoryQuizMenu()
menu.loop()
#menu.failed_when = False # do tego z zewnątrz nie nadpiszesz sobie przypadkowo tej "zmiennej" bo nie ma settera

0

Dekorator @property zamiast lambdy, szkoda że sam na to nie wpadłem.

Mam takie pytania:

  • Czy powinieniem używać jsona, xmla, sqlite zamiast pickle i shelve?
  • W jakich zastosowaniach lepiej jest używać yield, yield from zamiast return?
0

Czy powinienem używać jsona, xmla, sqlite zamiast pickle i shelve?

Tak. Każdy inny format lepiej reaguje na zmiany niż pickle. Ja z pickle nie korzystam bo to dla mnie nieporozumienie, że pozmieniasz coś w kodzie i nagle jest problem z dostępem do pliku z danymi bo ma starą strukturę. Niby nic nie do przejścia, ale więcej niepotrzebnej zabawy. Pozostałe formaty są lepsze w obsłudze, zawsze też jest łatwy do nich dostęp spoza aplikacji.

W jakich zastosowaniach lepiej jest używać yield, yield from zamiast return?

Gdy masz przejść w pętli po naprawdę dużej ilości danych. Dzięki temu program nie dławi się wszystkimi danymi jednocześnie. Porównanie masz np. tu: https://www.guru99.com/python-yield-return-generator.html

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