Probleme mit erstem Projekt. (Wecker)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Hillson
User
Beiträge: 22
Registriert: Dienstag 15. Mai 2018, 08:36

Okay.

Hier habe ich noch den neu geschriebenen Wecker mit allen Funktionen wie er sie vorher auch hatte. Ich habe eigentlich alle Hinweise von euch umgesetzt. Falls ihr Lust habt könnt ihr ja nochmal drüber schauen. Ansonsten empfinde ich es immer als sehr schwer selber rauszufinden, wo man noch etwas anders machen könnte und vor allem wie.

Code: Alles auswählen

import tkinter as tk
from playsound import playsound
from time import strftime, sleep
from functools import partial


class Alarm:
    def __init__(self, time, state):
        self.time = time
        self.state = state

    def change_state(self, state):
        self.state = state


def save(input_widget, database, main_listbox, window):

    alarm_time = input_widget.get()
    new_alarm = Alarm(alarm_time, True)
    input_widget.delete(0, tk.END)
    database.append(new_alarm)

    listbox_database = main_listbox.get(0, tk.END)

    if alarm_time not in listbox_database:
        main_listbox.insert(tk.END, alarm_time)
        new_listbox_database = main_listbox.get(0, tk.END)
        index = new_listbox_database.index(alarm_time)
        main_listbox.itemconfig(index, bg='green')

    timer(database, window, main_listbox)


def timer(database, window, main_listbox):

    for idx, alarm in enumerate(database):
        if alarm.state and alarm.time == strftime("%H:%M"):
            playsound("alarm.mp3")
            alarm.state = False
            main_listbox.itemconfig(idx, bg='red')

    window.after(500, timer, database, window, main_listbox)


def change_state(main_listbox, database):

    if database:
        listbox_selection = main_listbox.curselection()
        selected_alarm_index = listbox_selection[0]
        selected_timer = main_listbox.get(selected_alarm_index)

        for idx, alarm in enumerate(database):
            if alarm.time == selected_timer and alarm.state:
                alarm.state = False
                main_listbox.itemconfig(idx, bg='red')
            elif alarm.time == selected_timer and not alarm.state:
                alarm.state = True
                main_listbox.itemconfig(idx, bg='green')
    main_listbox.select_clear(0, 'end')


def delete_alarm(main_listbox, database):

    if database:
        listbox_selection = main_listbox.curselection()
        selected_alarm_index = listbox_selection[0]
        selected_timer = main_listbox.get(selected_alarm_index)

        for idx, alarm in enumerate(database):
            if alarm.time == selected_timer:
                del database[idx]
                main_listbox.delete(idx)
    main_listbox.select_clear(0, 'end')


def main():
    database = []

    window = tk.Tk()
    window.title("ALARM CLOCK")

    top_frame = tk.Frame(window)
    top_frame.pack()
    bottom_frame = tk.Frame(window)
    bottom_frame.pack(side=tk.BOTTOM)
    middle_frame = tk.Frame(window)
    middle_frame.pack(side=tk.BOTTOM)

    tk.Label(top_frame, text="Alarm (hh:mm)").pack()

    input_widget = tk.Entry(top_frame)
    input_widget.pack()

    main_listbox = tk.Listbox(middle_frame, height=5)
    main_listbox.pack()

    button = tk.Button(window, text="Save", command=partial(save, input_widget, database, main_listbox, window))
    button.pack()
    button_1 = tk.Button(bottom_frame, text="ON/OFF", command=partial(change_state, main_listbox, database))
    button_1.pack(side=tk.LEFT)
    button_2 = tk.Button(bottom_frame, text="DELET", command=partial(delete_alarm, main_listbox, database))
    button_2.pack(side=tk.LEFT)

    window.mainloop()


main()
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ein paar Anmerkungen:

- primitive setter wie "change_state" sind in Python ueberfluessig. Setz den direkt. Was du an anderer Stelle ja auch tust.
- in delete_alarm waehrend des iterierens ueber eine Liste diese mit del zu modifizieren ist ein Fehler. Dadurch geraet der Iterator durcheinander, der ist ja nur ein Index intern, und bezieht sich auf die Position in der Liste, auf die er zeigt. Die gleichzeitig zu aendern invalidiert den also implizit. Stattdessen erzeugt man ueblicherweise eine neue Liste mit den Elementen, die man behalten will, und weist die dann zu. Weil du aber ein gebundenes Argument hast, muss das nicht mit database = neue_database geschehen, sondern mit database[:] = neue_database
- der letzte Punkt zeigt, dass du im Grunde fuer das Projekt auf objektorientierte Programmierung zurueckgreifen willst, statt da diverse frei stehende Funktionen mit partial zu benutzen.
Hillson
User
Beiträge: 22
Registriert: Dienstag 15. Mai 2018, 08:36

Objektorientiert würde in dem Fall bedeuten, dass ich zum Beispiel für jeden Button eine extra Klasse erstelle mit speziellen Methoden? Also der Delete Button hat dann zum Beispiel eine Methode mit der er aus einer Liste Einträge löschen kann, der ON/OFF Button kann den Status vom Alarm ändern usw...

Und das ganze dann für die restlichen widgets auch noch oder?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Oh Gott nein. Die Zeiten wo man GUI Elemente abgeleitet hat sind vorbei. Es geht um die verwaltung der Wecker, also des Zustandes deines Programmes. Statt diverser Funktionen die alle möglichen Argumente angebunden bekommen, wird der Zustand im Objekt verwaltet und dessen Methoden können dann die callbacks werden.
Hillson
User
Beiträge: 22
Registriert: Dienstag 15. Mai 2018, 08:36

Gut damit kann ich im Moment nicht so viel anfangen außer das mein Gedanke nicht richtig war :D

Ich schau mir gerade mal nochmal alle meine Unterlagen aus dem Studium zu OOP durch. Vielleicht fällt mir dann ein wie ich das sinnvoll auf meinen Wecker anwenden kann.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

In `save` ist ein Bug. Du fügst den neuen Alarm immer `database` hinzu, aber dem Listbox nur dann, wenn die Alarmzeit noch nicht da ist. `change_state` und `delete_alarm` sind fast identisch, so dass man den gemeinsamen Code in eine neue Funktion auslagern kann. `sleep` wird nicht mehr gebraucht.

Code: Alles auswählen

import tkinter as tk
from time import strftime
from functools import partial
from playsound import playsound


class Alarm:
    def __init__(self, time, state):
        self.time = time
        self.state = state


def save(input_widget, database, main_listbox, window):
    alarm_time = input_widget.get()
    input_widget.delete(0, tk.END)
    if any(alarm.time == alarm_time for alarm in database):
        return
    new_alarm = Alarm(alarm_time, True)
    database.append(new_alarm)
    main_listbox.insert(tk.END, alarm_time)
    index = len(database) - 1
    main_listbox.itemconfig(index, bg='green')
    timer(database, window, main_listbox)


def timer(database, window, main_listbox):
    for idx, alarm in enumerate(database):
        if alarm.state and alarm.time == strftime("%H:%M"):
            playsound("alarm.mp3")
            alarm.state = False
            main_listbox.itemconfig(idx, bg='red')

    window.after(500, timer, database, window, main_listbox)


def get_alarm(main_listbox, database):
    listbox_selection = main_listbox.curselection()
    if not listbox_selection:
        raise IndexError()
    selected_alarm_index = listbox_selection[0]
    selected_timer = main_listbox.get(selected_alarm_index)

    for idx, alarm in enumerate(database):
        if alarm.time == selected_timer: 
            return idx, alarm
    raise IndexError()


def change_state(main_listbox, database):
    try:
        idx, alarm = get_alarm(main_listbox, database)
    except IndexError:
        pass
    else:
        alarm.state = not alarm.state
        main_listbox.itemconfig(idx, bg='green' if alarm.state else 'red')
    main_listbox.select_clear(0, 'end')


def delete_alarm(main_listbox, database):
    try:
        idx, alarm = get_alarm(main_listbox, database)
    except IndexError:
        pass
    else:
        del database[idx]
        main_listbox.delete(idx)
    main_listbox.select_clear(0, 'end')


def main():
    database = []

    window = tk.Tk()
    window.title("ALARM CLOCK")

    top_frame = tk.Frame(window)
    top_frame.pack()
    bottom_frame = tk.Frame(window)
    bottom_frame.pack(side=tk.BOTTOM)
    middle_frame = tk.Frame(window)
    middle_frame.pack(side=tk.BOTTOM)

    tk.Label(top_frame, text="Alarm (hh:mm)").pack()

    input_widget = tk.Entry(top_frame)
    input_widget.pack()

    main_listbox = tk.Listbox(middle_frame, height=5)
    main_listbox.pack()

    button = tk.Button(window, text="Save", command=partial(save, input_widget, database, main_listbox, window))
    button.pack()
    button_1 = tk.Button(bottom_frame, text="ON/OFF", command=partial(change_state, main_listbox, database))
    button_1.pack(side=tk.LEFT)
    button_2 = tk.Button(bottom_frame, text="DELET", command=partial(delete_alarm, main_listbox, database))
    button_2.pack(side=tk.LEFT)

    window.mainloop()


if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Und das ganze mit Klasse:

Code: Alles auswählen

from playsound import playsound
import tkinter as tk
from time import strftime, sleep
from functools import partial


class Alarm:
    def __init__(self, time, state):
        self.time = time
        self.state = state

class AlarmGUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.database = []
        self.title("ALARM CLOCK")
        top_frame = tk.Frame(self)
        top_frame.pack()
        bottom_frame = tk.Frame(self)
        bottom_frame.pack(side=tk.BOTTOM)
        middle_frame = tk.Frame(self)
        middle_frame.pack(side=tk.BOTTOM)
        tk.Label(top_frame, text="Alarm (hh:mm)").pack()

        self.input_widget = tk.Entry(top_frame)
        self.input_widget.pack()
        self.main_listbox = tk.Listbox(middle_frame, height=5)
        self.main_listbox.pack()
        button = tk.Button(self, text="Save", command=self.save)
        button.pack()
        button_1 = tk.Button(bottom_frame, text="ON/OFF", command=self.change_state)
        button_1.pack(side=tk.LEFT)
        button_2 = tk.Button(bottom_frame, text="DELET", command=self.delete_alarm)
        button_2.pack(side=tk.LEFT)

    def save(self):
        alarm_time = self.input_widget.get()
        self.input_widget.delete(0, tk.END)
        if any(alarm.time == alarm_time for alarm in self.database):
            return
        new_alarm = Alarm(alarm_time, True)
        self.database.append(new_alarm)
        self.main_listbox.insert(tk.END, alarm_time)
        index = len(self.database) - 1
        self.main_listbox.itemconfig(index, bg='green')
        self.timer()

    def timer(self):
        for idx, alarm in enumerate(self.database):
            if alarm.state and alarm.time == strftime("%H:%M"):
                #playsound("alarm.mp3")
                alarm.state = False
                self.main_listbox.itemconfig(idx, bg='red')
        self.after(500, self.timer)

    def get_alarm(self):
        listbox_selection = self.main_listbox.curselection()
        if not listbox_selection:
            raise IndexError()
        selected_alarm_index = listbox_selection[0]
        selected_timer = self.main_listbox.get(selected_alarm_index)

        for idx, alarm in enumerate(self.database):
            if alarm.time == selected_timer: 
                return idx, alarm
        raise IndexError()

    def change_state(self):
        try:
            idx, alarm = self.get_alarm()
        except IndexError:
            pass
        else:
            alarm.state = not alarm.state
            self.main_listbox.itemconfig(idx, bg='green' if alarm.state else 'red')
        self.main_listbox.select_clear(0, 'end')

    def delete_alarm(self):
        try:
            idx, alarm = self.get_alarm()
        except IndexError:
            pass
        else:
            del self.database[idx]
            self.main_listbox.delete(idx)
        self.main_listbox.select_clear(0, 'end')


def main():
    window = AlarmGUI()
    window.mainloop()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hillson hat geschrieben: Donnerstag 2. Januar 2020, 15:37

Code: Alles auswählen

for i in range(3):
    callbacks.append(lambda: print(i))
    #callbacks.append(partial(print, i))
Hier nach müsste callbacks doch so aussehen oder nicht:

Code: Alles auswählen

[lambda: print(0),lambda: print(1),lambda: print(2)]
lambda muss doch hier eigentlich gar nichts speichern, weil der Eintrag in die Liste doch direkt nach jedem Durchlauf gemacht wird und damit i für den Eintrag doch auch fest sein sollte. Achso oder ist das so gemeint mit dem Speichern, dass bei lambda pro Funktion immer nur die letzte Definition des Arguments übernommen wird?
So sieht `callbacks` aber nicht aus, sondern so:

Code: Alles auswählen

[lambda: print(i), lambda: print(i), lambda: print(i)]
Das `i` wird erst beim Aufruf zu einem Wert aufgelöst und zwar wird da in dem Namensraum geschaut in dem der `lambda`-Ausdruck ausgeführt wurde. Und da ist `i` am Ende der Schleife an den Wert 2 gebunden.

Ein Beispiel für ein Closure wäre eine (naive) Implementierung der `partial()`-Funktion:

Code: Alles auswählen

def partial(function, *arguments, **keyword_arguments):
    return lambda *args, **kwargs: function(*arguments, *args, **keyword_arguments, **kwargs)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Hillson
User
Beiträge: 22
Registriert: Dienstag 15. Mai 2018, 08:36

Vielen Dank für die ausführliche Hilfe.

Ich werde mir dann jetzt ein neues Projekt suchen.
Antworten