Schulnoten Tabelle

Code-Stücke können hier veröffentlicht werden.
Antworten
marlene
User
Beiträge: 26
Registriert: Montag 13. September 2010, 12:11

Grüss euch,

ich habe mich an einer Übung probiert. Im einzelenen soll man mit dem Programm Schulfächer als Instanzen anlegen und speichern, löschen(Schulfach), noten vergeben und löschen, und den durchschnitt berechnen können.

Das ganze Funktioniert auch. Hab leider aber noch fast keine Erfahrung mit Klassen und OOP usw.

Wer Lust hat kann sich das ja mal anschauen und "schimpfen" :roll:

Code: Alles auswählen

import pickle

class Subject(object):
    def __init__(self):
        self.ex = []
        self.sa = []
        self.average = 0

    def new_grade(self):
        self.grade = raw_input('Neue (sa) oder (ex) Note: ')
        if self.grade == 'ex':
            self.ex.append(int(raw_input('Neue ex Note: ')))
        if self.grade == 'sa':
            self.sa.append(int(raw_input('Neue sa Note: ')))
            
    def del_grade(self):
        self.grade= raw_input('Loesche (sa) oder (ex) Note: ')
        if self.grade == 'ex':
            self.ex.remove(int(raw_input('Loesche ex Note: ')))
        if self.grade == 'sa':
            self.sa.remove(int(raw_input('Loesche sa Note: ')))
        
    def show_grades(self):
        print('Ex Noten: ')
        for self.grade in self.ex:
            print self.grade
        print('Sa Noten: ')
        for self.grade in self.sa:
            print self.grade

    def calculate_average(self):
        self.count = 0
        if len(self.sa) >= 1:
            self.average_sa = (float(sum(self.sa)/len(self.sa)))
            self.count += 2
        else:
            self.average_sa = 0
        if len(self.ex) >= 1:
            self.average_ex = (float(sum(self.ex)/len(self.ex)))
            self.count += 1
        else:
            self.average_ex = 0
        self.average = (float(self.average_sa*2) + float(self.average_ex))/int(self.count)
        print
        print('Der Durchschnitt ist %s') % (str(self.average))


def main():
    subject_dict = {}
    while True:
        print'''
    (1)Neues Fach anlegen
    (2)Fach loeschen
    (3)Alle Facher anzeigen
    (4)Noten eintragen/loeschen
    (5)Noten anzeigen
    (6)Durchschnitt berechnen
    (7)Speichern
    (8)Laden
    (9)Exit
            '''
        choice = raw_input('Eingabe: ')
        if choice == '1':
            subject = raw_input('Name des Faches: ')
            subject_dict[subject] = Subject()
        if choice == '2':
            subject = raw_input('Name des Faches: ')
            del subject_dict[subject]
        if choice == '3':
            for keys in subject_dict.keys():
                print keys
        if choice == '4':
            subject = raw_input('Name des Faches: ')
            while True:
                in_out = raw_input('(e)eintragen/(l)loeschen/(f)fertig: ')
                if in_out == 'e':
                    subject_dict[subject].new_grade()
                if in_out == 'l':
                    subject_dict[subject].del_grade()
                if in_out == 'f':
                    break
        if choice == '5':
            subject = raw_input('Name des Faches: ')
            subject_dict[subject].show_grades()
        if choice == '6':
            subject = raw_input('Name des Faches: ')
            subject_dict[subject].calculate_average()
        if choice == '7':
            output = open('noten.pkl', 'wb') 
            pickle.dump(subject_dict, output)
            output.close()
            print('Datei gespeichert')
        if choice == '8':
            pkl_file = open('noten.pkl', 'rb')
            subject_dict = pickle.load(pkl_file)
            print('Datei geladen')
        if choice == '9':
            exit()

if __name__ == '__main__':
    main()
OverNord
User
Beiträge: 72
Registriert: Donnerstag 24. Januar 2008, 11:59
Kontaktdaten:

Hallo,

am schlimmsten finde ich die main-funktion, ein dict mit ein paar funktionen würde würde sich dort besser machen. Die Datei die du zum lesen öffnest wird nicht geschlossen. Siehe dir mal das with-statement an. Außerdem bin ich der Meinung, dass Ein- und Ausgabe nichts in der Klasse Subject zu suchen hat. Und es gibt elif.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Desweiteren finde ich die prints und inputs in der Subject-Klasse nicht schön. Allerdings kapiere ich den Sinn hinter Subject eh noch nicht. Was sind denn "ex" und "sa" für Namen? Bist Du sicher, dass Du die in der Zukunft noch kapierst?

Zudem finde ich es unschön, dass in quasi jeder Methode neue Instanzvariablen eingeführt werden: self.count, sefl.grade, ...

Die Tupel-Klammern nach dem print-Statement finde ich auch nicht gut. Das suggeriert, print wäre eine Funktion - das ist es aber erst ab 3.x, welches Du augenscheinlich nicht verwendest (class erbt von object, raw_input). Wenn Du denn willst, dann importiere es:

Code: Alles auswählen

from __future__ import print_function
Neben wenig aussagekräftiger Namen fehlen einfach auch Kommentare. So muss man sich mühsam durch den Code wühlen, um zu kapieren, was Du da überhaupt machst.

Ich würde zudem den Namen der Datei per Kommandozeilen-Parameter übergeben. Entweder mit argparse oder optparse oder stumpf per sys.argv[1], wenn's denn ganz trivial bleiben soll.

Wie OverNord es schon ansprach, könnte und sollte man das Menü anders gestalten. Mach doch eine Liste von Tupeln daraus (ungetestet)

Code: Alles auswählen

from operator import itemgetter

opt_name, func = map(itemgetter, (0, 1))

# Funktionen müssen natürlich existieren
# ggf. kann man das ein oder andere auch per lambda lösen
MENU_ITEMS = [
    ("Neues Fach anlegen", add_class),
    ("Fach loeschen", del_class),
    ...
]
# kann man evtl. auch per "".join() und einem Generator machen
for index, option in enumerate(MENU_ITEMS, 1):
    print "({0}) {1}".format(str(index), opt_name(option))
# bei der Auswahl:
choice = int(raw_input("...")) - 1
func(MENU_ITEMS[choice])()
Anstelle der Itemgetter kann man natürlich auch einfach per Index auf die notwendigen Felder zugreifen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
marlene
User
Beiträge: 26
Registriert: Montag 13. September 2010, 12:11

Hi,

ich hätte gedacht das in einer Klasse, die Ein und Ausgabe von Daten in ordnung wäre?

Das with-statement kannte ich nicht, benutze ich aber jetzt.


Der Sinn hinter Subject:

Ich wollte(habe):
Eine Klasse Subject

in der ich folgende Attribute habe:
self.ex ist eine extemporale also eine Kurzareit
self.sa ist eine Schulaufgabe
self.average ist der Durchschnitt des jeweiligen Faches

Nun hab ich meine Methoden um neue Noten anzulegen oder Noten auszugeben, den durschnitt berechnen usw.
Deswegen ist mir auch nicht so klar warum die Ausgabe oder Eingabe(in einer Klasse) von Daten nicht ok ist?

Jetzt will ich mittels Menüsteuerung eine Neue Instanz erstellen können, z.B. deutsch = Subject().
Die Instanzen(Fächer) werden dann in einem Dict gepeichert.
Durch Auswahl anderer Optionen möchte ich dann die Fächer manipulieren, also Noten hinzufügen, löschen usw. Oder das Dict in einer Datei speichern oder aus einer Datei heraus laden.

Die Menüsteuerung bei mir war schlecht, ich habe jezt mal die von Hyperion ausprobiert. Auf solche Lösungen komm ich leider noch nicht, da fehlt mir schon noch einiges an Übung.


Danke für Eure Antworten
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also ich kapiere noch nicht, was diese Listen denn repräsentieren. Sind das quasi Noten für eine Person?

Deine del_grade()-Methode löscht das erste Auftreten einer Note - ist das beabsichtigt? Evtl. spielt der Verlauf ja eine Rolle, um Tendenzen abzuleiten. Da würde ich dann das Löschen über den Index vorschlagen.

Ich würde auch versuchen die Mischung aus deutschen und englischen Namen aufzulösen. Wie wäre es mit exercises und small_execises? Oder regular_grades und small_grades? Halt irgendetwas aussagekräftigeres als "se" oder "sa"

Dein average ist prädesteniert für eine read-only Property:

Code: Alles auswählen

In [49]: class Subject(object):
   ....:     @property
   ....:     def average(self):
   ....:         return sum(self.grades) / len(self.grades)
   ....:     

In [57]: s = Subject()

In [58]: s.grades = [1,2,2,4,2,2]

In [59]: s.average
Out[59]: 2

In [60]: s.average = 4
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

/home/nelson/src/Python/Snippets/subdemo/<ipython console> in <module>()

AttributeError: can't set attribute
Du hast bei Deiner Berechnung auch eine Code-Wiederholung:

Code: Alles auswählen

self.average_sa = (float(sum(self.sa)/len(self.sa)))
#
self.average_ex = (float(sum(self.ex)/len(self.ex)))
Das sieht doch ziemlich gleich aus, gel ;-) Das zu vermeiden ist einer der "Hauptkünste" beim Programmieren. Du musst versuchen, das zu erkennen und zu generalisieren. Beide Berechnungen unterscheiden sich nur hinsichtlich des Objektes, welches als Berechnungsgrundlage dient. Dieses kann man doch aber einer Funktion als Parameter übergeben und schon hat man eine universelle Lösung:

Code: Alles auswählen

def arithmetic_average(values):
    try:
         return float(sum(values) / len(values))
    except ZeroDivisionError:
        # k.A. ob das mathematisch sinnvoll ist
        return 0
Das gleiche kannst Du dann noch für das geometrische Mittel machen (also das gewichtete). Da müßte man sich überlegen, wie man am geschicktesten die Werte für die Gewichtung mit den Notenlisten koppelt. Also evtl. Paare aus Gewichtung, Liste oder eine separate Liste in gleicher Reihenfolge (imho weniger hübsch).

show_grades() würde ich in __str__ "umbennen"; das ist die Standard-Methode, um ein Objekt ausführlich als String zu beschreiben.

Ich würde die Eingaben wirklich aus der Klasse rausnehmen. Stell Dir vor, Du willst aus einer externen Quelle Daten eingeben oder löschen. Dann müßtest Du diese Schritt für Schritt manuell eingeben - wenig sinnvoll, oder? Also schreib eben Methoden, die das Hinzufügen oder Löschen durch Parameter realisieren. Für die manuelle Eingabe schreibst Du Dir eben eine (möglichst) universelle Funktion, die ein Subject-Objekt entgegen nimmt und dann eben die Eingaben auswertet und die Werte im Objekt über diese Methoden manipuliert.

Beachte doch auch noch die anderen Tipps, z.B. das wegen print, von oben :-)

Ich würde übrigens ein Attribut "name" in die Klasse integrieren. Denn der Name des Objekts im Quellcode ist ja nicht von Belang, sondern Du willst ja auch irgend wie wissen / sehen, um welches Fach es sich handelt ;-)

Wenn Du Dein Menü über Indizes steuerst, wäre es evtl. eine gute Idee, das auch bei der Fachauswahl so zu handhaben, sprich dort auf das Dictionary verzichten und einfach auch eine Liste führen. Dadurch könntest Du den Code fürs Menü quasi recyclen für die Fachauswahl :-)

Ansonsten noch viel Erfolg! :-)

BTW: Schau Dir doch mal JSON an - nichts gegen pickle, aber da kannst Du auch mal schnell im Klartext Noten hinzufügen oder ändern usw :-)
Zuletzt geändert von Hyperion am Samstag 26. Februar 2011, 18:47, insgesamt 1-mal geändert.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

So implementiert BlackJack die Funktion average:
http://bj.spline.de/python_openbook.html#schlechtes-beispiel-f-r-objektorientierung hat geschrieben:

Code: Alles auswählen

def average(iterable, start_value=0):
    total = start_value
    i = None
    for i, value in enumerate(iterable):
        total += value
    if i is None:
        raise ValueError('iterable must contain at least one argument.')
    return total / (i + 1)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ah... sofern len() die Liste einmal durchgeht ist das natürlich von der Laufzeit die schönere Variante!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Hyperion hat geschrieben:Ah... sofern len() die Liste einmal durchgeht ist das natürlich von der Laufzeit die schönere Variante!
Da ist der Punkt: BlackJack geht nicht von einer Liste aus, sondern von einem beliebigen Iterable, also auch Generatoren, etc und damit Objekte, die `len` nicht unterstuetzen oder verbrauchbar sind.

Allerdings nutzt er auch aus, dass das `i` leakt, koennte zu Problemen bei anderen Python-Implementierungen fuehren.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

cofi hat geschrieben: Allerdings nutzt er auch aus, dass das `i` leakt, koennte zu Problemen bei anderen Python-Implementierungen fuehren.
Was meinst Du mit "leakt"? Kannst Du mich da mal erhellen?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Das `i` wird in der Schleife erzeugt, aber nach der Schleife gibt es das noch.
So wie bei

Code: Alles auswählen

int i;
for(i = 0; i < 10; ++i)
    ;
anders als bei

Code: Alles auswählen

for (int i = 0; i < 10; ++i)
    ;
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Danke :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

cofi: Das tut er nicht. i wird vor der Schleife mit "i = None" initialisiert.

Edit: Du hast doch recht, ich habe die letzte Zeile des Codes nicht genau gelesen. :oops:
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

derdon hat geschrieben:cofi: Das tut er nicht. i wird vor der Schleife mit "i = None" initialisiert.
Hmm das hab ich uebersehen. Ich zieh meine Kritik zurueck, denn damit leakt der Name ja nicht mehr.
BlackJack

@cofi: Der Name beziehungsweise auch der Wert "leaken" auf jeden Fall weil in Python eine ``for``-Schleife keinen neuen lokalen Namensraum erzeugt.

Mit anderen Python-Implementierungen kann man da eigentlich auch keine Probleme bekommen, denn neue Namensräume werden nur von ``class``, ``def``, und Generatorausdrücken erzeugt. In Python 3.x auch noch von "list comprehensions".
Newcomer
User
Beiträge: 131
Registriert: Sonntag 15. Mai 2011, 20:41

Hyperion hat geschrieben:
Die Tupel-Klammern nach dem print-Statement finde ich auch nicht gut. Das suggeriert, print wäre eine Funktion - das ist es aber erst ab 3.x, welches Du augenscheinlich nicht verwendest (class erbt von object, raw_input). Wenn Du denn willst, dann importiere es:
Also ich dachte immer, dass bei 3.x class auch von object erben kann
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Newcomer hat geschrieben:
Hyperion hat geschrieben:
Die Tupel-Klammern nach dem print-Statement finde ich auch nicht gut. Das suggeriert, print wäre eine Funktion - das ist es aber erst ab 3.x, welches Du augenscheinlich nicht verwendest (class erbt von object, raw_input). Wenn Du denn willst, dann importiere es:
Also ich dachte immer, dass bei 3.x class auch von object erben kann
Ja, aber es ist unnoetig, da man dort immer von `object` erbt.
Newcomer
User
Beiträge: 131
Registriert: Sonntag 15. Mai 2011, 20:41

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

Newcomer hat geschrieben: Also ich dachte immer, dass bei 3.x class auch von object erben kann
Mag sein, dass man das kann - man braucht es aber zumindest nicht mehr.

Edit: Ok, cofi war schneller :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten