OOP - aber wie?

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.
Antworten
pythonstarter
User
Beiträge: 53
Registriert: Donnerstag 15. April 2010, 20:34

Hallo, habe als Python-Einstieg ein kleines Programm zur Ermittlung des Energie/Nährstoffbedarfes geschrieben. Dabei habe ich (weil ich's noch nicht besser kann) auf OOP komplett verzichtet. Jetzt brüte ich darüber, wo bzw. wie ich hier mit objektorientiertem Code Platz/Zeit/und Arbeit hätte sparen können.
Danke für die Tipps :D

Code: Alles auswählen

import sys, tkinter, tkinter.messagebox, time
from tkinter import*

def ende ():
    sys.exit(0)

#Funktion für die Berechnung 
def Berechnung ():

#Richtwerte
    richtwertWasser = 40
    richtwertWasser = int(richtwertWasser)

    richtwertAmino = 1.2
    richtwertAmino = float(richtwertAmino)

          
#Eingabefelder mit Exception

    groesse = e4.get()
    try:
        groesse = float (groesse)
    except:
        tkinter.messagebox.showwarning ("Achtung", "Bitte Größe eingeben")
        
    gewicht = e5.get()
    try:
        gewicht = float (gewicht)
    except:
        tkinter.messagebox.showwarning ("Achtung", "Bitte Gewicht eingeben")


    alter = eAlter.get()
    try:
        alter = int(alter)
    except:
        tkinter.messagebox.showwarning ("Achtung", "Bitte Alter eingeben")
        

#Berechnung Wasserbedarf
    wasserBedarf = richtwertWasser * gewicht
    lbWasser ["text"] = "Tagesbedarf Wasser: " + str(wasserBedarf) + "ml/Tag"
	
#Naehrstoff Eingabefeld
    naehrstoff = str(scvwert.get())
    naehrstoff = float (naehrstoff)

    bedarfAmino = gewicht* richtwertAmino *(naehrstoff/100)
    lbAmino["text"] = "Energie aus Aminosäuren: " + str (bedarfAmino) + "kcal/Tag " 

    
#Funktion für BMI
    bmi = gewicht /( (groesse/100) * (groesse/100))
    bmi = round (bmi, 2)
    lbbmi ["text"] = "Der BMI betraegt: " + str(bmi)

    if bmi < 18:
        tkinter.messagebox.showwarning ("Achtung", "BMI zu niedrig!")
    elif bmi > 25:
        tkinter.messagebox.showwarning ("Achtung", "BMI zu hoch!")

#Funktion Grundumsatz    
    
    if geschlecht.get() == "m":
        grumsatz = 66+(13.7*gewicht)+5*groesse-6.8*alter
        grumsatz = round (grumsatz, 2)
        lbGrundumsatz ["text"] = "Grundumsatz: " + str (grumsatz) + "kcal/Tag"
    else:
        grumsatz = 655+(9.6*gewicht)+1.8*groesse-4.7*alter
        grumsatz = round (grumsatz, 2)
        lbGrundumsatz ["text"] = "Grundumsatz: " + str (grumsatz) + "kcal/Tag"
    


    richtwertFett = float (0.4)
    faktorEnergie = energiewert.get()
    faktorEnergie = float(faktorEnergie)
    energiezufuhr = (faktorEnergie*grumsatz)*(naehrstoff/100)
    energiezufuhr = float(energiezufuhr)

#Energie aus Fett    
    bedarfFett = richtwertFett*energiezufuhr
    bedarfFett = float(bedarfFett)
    bedarfFett = round(bedarfFett, 2)
    lbFett["text"] = "Energie aus Fett: " + str(bedarfFett) + "kcal/Tag"


#Energiezufuhr pro Tag für AS, Fett, KH
    energieAS_Tag = richtwertAmino*gewicht*naehrstoff/100
    energieAS_Tag = float(energieAS_Tag)

    energieFett_Tag = bedarfFett/9
    energieFett_Tag = float (energieFett_Tag)

    asFrei = energiezufuhr-energieAS_Tag*4
    asFrei = float(asFrei)

    energieKH = asFrei-bedarfFett
    energieKH = float (energieKH)
    

    lbKH ["text"] = "Energie aus KH: " + str(energieKH) + "kcal/Tag"
    
#Datumtsanzeige
lt = time.localtime()
jahr, monat, tag, stunde, minute, sekunde = lt[0:6]
datum = ("{0:02d}.{1:02d}.{2:4d}, {3:02d}:{4:02d}:{5:02d}".format (tag,monat,jahr, stunde, minute, sekunde))


main = tkinter.Tk()
main.title("Toms kleine Helferlein - Ernährung")


lb_time_text = tkinter.Label (main, text = "Datum/Zeit")
lb_time_text.grid(row = 1, column = 5, sticky = "w")

lb_time = tkinter.Label(main, text = datum)
lb_time.grid(row = 2, column = 5, sticky = "w")


#Name Label mit Text
lb1 = tkinter.Label(main, text = "Name")
lb1.grid(row = 0, column = 0, sticky="w")
lb1 ["font"] = "Courier 8"
lb1 ["height"] = 2
lb1 ["borderwidth"] = 2


#Name Eingabefeld
e1 = tkinter.Entry (main)
e1.grid(row = 0, column = 1, sticky="w")
e1 ["borderwidth"] = 2
e1 ["width"] = 20
e1 ["relief"] = "groove"

eAlter = tkinter.Entry(main)
eAlter.grid(row = 0, column = 3, sticky = "w")
eAlter ["borderwidth"] = 2
eAlter ["width"] = 4
eAlter ["relief"] = "groove"

lbAlter = tkinter.Label(main, text = "Alter")
lbAlter.grid(row = 0, column = 2, sticky = "w")
lbAlter ["font"] = "Courier 8"
lbAlter ["height"] = 2
lbAlter ["borderwidth"] = 2


lbgeschl = tkinter.Label(main, text = "Geschlecht")
lbgeschl.grid (row = 1, column = 0, sticky="w")
lbgeschl ["font"] = "Courier 8"
lbgeschl ["height"] = 2
lbgeschl ["borderwidth"] = 2


#Radiobutton für Geschlecht
geschlecht = tkinter.StringVar()
geschlecht.set("m")

rbm = tkinter.Radiobutton(main, text = "männlich", variable=geschlecht, value = "m")
rbm.grid (row = 1, column = 1, sticky="w")

#Radiobutton für GEschlecht weiblich
rbw = tkinter.Radiobutton(main, text = "weiblich", variable = geschlecht, value = "w")
rbw.grid (row = 2, column = 1, sticky = "w")

#Diagnose Label mit Text
lb2 = tkinter. Label(main, text = "Diagnose")
lb2.grid (row = 3, column = 0, sticky="w")
lb2 ["font"] = "Courier 8"
lb2 ["height"] = 2
lb2 ["borderwidth"] = 2


#Diagnose Eingabefeld
e2 = tkinter.Entry (main)
e2.grid(row = 3, column = 1, sticky="w")
e2 ["borderwidth"] = 2
e2 ["relief"] = "groove"


#Nebendiagnose Label mit Text
lb3 = tkinter.Label(main, text = "Nebendiagnose(n)")
lb3.grid (row = 4, column = 0, sticky="w")
lb3 ["font"] = "Courier 8"
lb3 ["height"] = 2
lb3 ["borderwidth"] = 2

#Nebendiagnose Eingabefeld
e3 = tkinter.Entry (main)
e3.grid (row = 4, column = 1, sticky="w")
e3 ["borderwidth"] = 2
e3 ["relief"] = "groove"


#Groesse Label mit Text
lb4 = tkinter.Label(main, text = "Groesse")
lb4.grid (row = 5, column = 0, sticky="w")
lb4 ["font"] = "Courier 8"
lb4 ["height"] = 2
lb4 ["borderwidth"] = 2


#Groesse Eingabefeld
e4 = tkinter.Entry (main)
e4.grid (row = 5, column = 1, sticky="w")
e4 ["borderwidth"] = 2
e4["relief"] = "groove"


#Gewicht Label mit Text
lb5 = tkinter.Label(main, text = "Gewicht")
lb5.grid (row = 6, column = 0, sticky="w")
lb5 ["font"] = "Courier 8"
lb5 ["height"] = 2
lb5 ["borderwidth"] = 2


#Gewicht Eingabefeld
e5 = tkinter.Entry (main)
e5.grid (row = 6, column = 1, sticky="w")
e5 ["borderwidth"] = 2
e5["relief"] = "groove"


#Label Energiezufuhr mit Faktor 1,2 - 1,5. Richtwert ist 1,2
lenergie = tkinter.Label(main, text = "Faktor Energiezufuhr")
lenergie.grid(row = 7, column = 0, sticky = "w")
lenergie ["font"] = "Courier 8"
lenergie ["height"] = 2
lenergie ["borderwidth"] = 2

#Schieberegler Energiezufuhr von 1,2 bis 1,5
energiewert = tkinter.DoubleVar()
energiewert.set(1.2)

energie = tkinter.Scale(main, width = 10, length = 100, orient = "horizontal",
                    from_=1.2, to = 1.5, resolution = 0.1, tickinterval = 0.1,
                    label = " ", variable = energiewert)
energie.grid (row = 7, column = 1, sticky="w")


#Schieberegler Nährstoffbedarf = scv
scvwert = tkinter.IntVar()
scvwert.set(100)

scv = tkinter.Scale(main, width = 10, length = 100, orient = "horizontal",
                    from_=0, to = 100, resolution = 5, tickinterval = 50,
                    label = " ", variable = scvwert)
scv.grid (row = 8, column = 1, sticky ="w")


#Nährstoffzufuhr in Prozent
lb6 = tkinter.Label(main, text = "Nährstoffbedarf in %")
lb6.grid (row = 8, column = 0, sticky="w")
lb6 ["font"] = "Courier 8"
lb6 ["height"] = 2
lb6 ["borderwidth"] = 2


#Überschrift über den berechneten Ausgaben
lbHeadRechn = tkinter.Label(main, text = "Gesamtbedarf")
lbHeadRechn["font"] = "Courier 8 bold"
lbHeadRechn.grid(row = 9, column = 0, sticky = "w")

#Button fuer Berechnung einrichten
baminoBedarf = tkinter.Button(main, text = "Berechnung", command = Berechnung)
baminoBedarf.grid (row = 9, column = 1, sticky="w")


#Ausgabe BMI
lbbmi = tkinter.Label(main, text = "Der BMI betraegt: ")
lbbmi ["height"] = 2
lbbmi.grid (row = 10, column = 0, sticky="w")


#Ausgabe Wasserbedarf
lbWasser = tkinter.Label(main, text = "Tagesbedarf Wasser: ml/Tag ")
lbWasser ["height"] = 2
lbWasser.grid (row = 11, column = 0, sticky="w")


#Ausgabe Aminosäuren
lbAmino = tkinter.Label(main, text = "Energie aus AS: kcal/Tag ")
lbAmino ["height"] = 2
lbAmino.grid (row = 12, column = 0, sticky="w")

#Ausgabe Fett
lbFett = tkinter.Label(main, text = "Energie aus Fett: kcal/Tag")
lbFett ["height"] = 2
lbFett.grid (row = 13, column = 0, sticky="w")

lbKH = tkinter.Label(main, text = "Energie aus KH: kcal/Tag")
lbKH ["height"] = 2
lbKH.grid(row = 16, column = 0, sticky = "w")

#Ausgabe Grundumsatz
lbGrundumsatz = tkinter.Label(main, text = "Grundumsatz: kcal/Tag")
lbGrundumsatz ["height"] = 2
lbGrundumsatz.grid (row = 15, column = 0, sticky = "w")


scb = tkinter.Scrollbar(main, orient = "vertical")

def anzeigen():
    for x in li.curselection():
        lb = tkinter.Label(main, text = "")
        lb ["borderwidth"] = 2
        lb ["relief"] = "sunken"
        lb ["text"] = li.get(x)
        lb.grid (row = 6, column = 4)
        lb = tkinter.Label(main, "x")
    

# erzeugt gesamte Menuleiste
mBar = tkinter.Menu(main)


# erzeugt erstes Menuobjekt der Menuleiste
mFile = tkinter.Menu(mBar)

# erzeugt Elemente in erstem Menu
mFile.add_command(label="Neu")
mFile.add_command(label="Laden")
mFile.add_command(label="Speichern")
mFile.add_separator()
mFile.add_command(label="Beenden", command=ende)

# Widget-Variablen der Radiobutton-Menupunkte
# bzw. Checkbutton-Menupunkte
farbe = tkinter.StringVar()
farbe.set("#FFFFFF")
rand = tkinter.IntVar()
rand.set(0)

# erzeugt zweites Menuobjekt der Menuleiste
mView = tkinter.Menu(mBar)
mView["tearoff"] = 0     # Menu nicht abtrennbar

# erzeugt Elemente in zweitem Menu


# erstes und zweites Menu zur Menuleiste hinzu
mBar.add_cascade(label="Datei", menu=mFile)
mBar.add_cascade(label="Ansicht", menu=mView)

# gesamte Menuleiste zu Fenster hinzu
main["menu"] = mBar


main.mainloop()



Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Es gibt spezielle Code-Tags für Python-Quellcode. Damit hat man schöneres Syntax-Highlighting.

Also zunächst einmal, nutzt Du OOP, ohne es zu "wissen" ;-) Du rufst ja z.B. Methoden von Objekten auf und arbeitest mit Objekten von Klassen. Man muss nicht zwangsläufig (eigene) Klassen implementieren, um OOP zu praktizieren. In Python ist übrigens zu ziemlich alles ein Objekt :-)

Generell solltest Du aber den Code noch mal in vielen Iterationen überarbeiten. Zunächst einmal schau Dir mal PeP8 an. Das ist der Python-Style-Guide, der einen Standard für die Quellcode-Gestaltung darstellt. Funktionen z.B. werden laut PEP8 in lower_case geschrieben.

Kommentare sollten "sinnvoll" sein; einen Kommentar "Funktion für die Berechnung" über die Funktion "Berechnung" zu schreiben ist wenig informativ. Das das ganze eine Funktion ist, erkennt man ja leicht am "def" und der Name steht dann dahinter. Sinnvolle Kommentare sind mit das schwerste, aber helfen einem bereits bei der Strukturierung. Denn wenn Du merkst Dein Kommentar beschreibt etwas sinnloses, brauchst Du es ja auch nicht wirklich so umzusetzen.

Zudem sollte man bei Modulen, Klassen und Funktionen eher auf die Doc-Strings zurückgreifen:

Code: Alles auswählen

def calculate():
    """
    here we can describe how this function works...
    """
Funktionen, die Objekte zurückliefern, die man für eine andere Funktion benötigt, kann man kaskadierend schreiben. Das erspart einem mehrfaches "zwischenspeichern":

Code: Alles auswählen

# bei dir:
naehrstoff = str(scvwert.get())
naehrstoff = float (naehrstoff)
# besser:
naehrstoff = float(str(scvwert.get()))
Wobei ich mich frage, was scvwert.get() eigentlich für einen Typen zurückliefert... also muss man hier wirklich den Umweg über str() nehmen?

So was hier ist komplett unnötig:

Code: Alles auswählen

    richtwertWasser = 40
    richtwertWasser = int(richtwertWasser)
richtwertWasser ist doch bereits ein Integer! Wozu also noch mal die Konvertierung?

ein try...except ohne konkrete Angabe, welche Exception man abfangen will, sollte man nie in seinen Code schreiben. Somit fängst Du alles ab - und evtl. tritt ja eine Ausnahme auf, die Du gar nicht bedacht hattest. Insofern immer den Typen angeben, den Du abfangen willst.
Hier mal ein Beispiel von Dir:

Code: Alles auswählen

    groesse = e4.get()
    try:
        groesse = float (groesse)
    except:
        tkinter.messagebox.showwarning ("Achtung", "Bitte Größe eingeben")
Welchen Fehler willst Du hier abfangen?

Generell würde ich versuchen, die Aktion, die das Berechnen antößt nur dann verfügbar zu machen, wenn man alle notwendigen Felder ausgefüllt hat.

Wenn Du bei all diesen Feldern den selben Fehler abfangen willst, kannst Du die Konvertierung zu float auch hintereinander in ein "try" schreiben.

Ich kenne mich mit TkInter nicht aus, aber bei Qt kann man z.B. bein Eingabefeldern definieren, was für Werte(bereiche) darin eingetragen werden können.

Generell würde ich den Code von Modulebene verschwinden lassen. Dazu gibt es den

Code: Alles auswählen

# wenn wir das Modul als Script starten...
if __name__ == "__main__":
    # gehen wir in diese Funktion (die auch anders heißen darf)
    main()
"Trick".

Generell gefällt mir die Nummerierung bei den Eingabefeldern nicht. Wenn man nummeriert, ist das oftmals ein Indiz, dass man eigentlich eine andere Datenstruktur (wie etwa eine Liste) verwenden sollte. Du könntest Deine Label durchaus in einer Liste / Dict speichern scheint mir, da Du ja eigentlich immer die gleichen Werte verwendest und nur bei einigen Attributen andere Werte setzt. Also könnte man Funktionen schreiben, die einem ein Label / Eingabefeld-Objekt zurückliefern und gewisse Parameter akzeptieren. Diese packst Du vorher in eine geeignete Datenstruktur. Die Rückgabewerte merkst Du Dir ggf. in einem Dict, um gezielt über einen Namen auf ein Feld und den dortigen wert zugreifen zu können.

Generell bin ich kein Freund von GUI-Erstellung per Hand. Allerdings weiß ich nicht, ob es für tkinter einen GUI-Designer gibt?

So, das nur auf die Schnelle. Konnte es leider nicht testen, da Du wohl Python 3 verwendest ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
pythonstarter
User
Beiträge: 53
Registriert: Donnerstag 15. April 2010, 20:34

Vielen dank für die schnelle Antwort - werde mich mal bemühen, die Tipps umzusetzen.
Allerdings habe ich das untenstehende noch nicht kapiert. Was macht denn diese Anweisung?
Hyperion hat geschrieben:
Generell würde ich den Code von Modulebene verschwinden lassen. Dazu gibt es den

Code: Alles auswählen

# wenn wir das Modul als Script starten...
if __name__ == "__main__":
    # gehen wir in diese Funktion (die auch anders heißen darf)
    main()
"Trick".
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Habe ich doch halbwegs kommentiert!?!

Aber wie so oft: Probiers doch einfach mal aus! :-)

Modul foo.py

Code: Alles auswählen

def hello(name="Welt"):
    print "Hallo {0}".format(name)

# testen
hello("Chris")
Das gleiche als Modul bar.py, aber mit dem oben genannten hook:

Code: Alles auswählen

def hello(name="Welt"):
    print "Hallo {0}".format(name)

if __name__ == "__main__"
    hello("Chris")
Wenn Du nun mal beide Module in einer Python Shell importierst, wirst Du sehen, dass bei Variante "bar" keine Textausgabe kommt. Python kapiert, dass das ganze importiert wird und somit ist der name "__name__" eben nicht "__main__"! Damit wird eben nicht die Funktion für einen Test aufgerufen. Genau das Verhalten wünscht man sich idR.

Führst Du aber beide als Script aus, so verhalten sie sich identisch.

So einfach ist das eigentlich :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten