Wartezeiten von Patienten auflisten und verwerten

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
Didres
User
Beiträge: 5
Registriert: Donnerstag 29. Oktober 2015, 12:26

Hallöchen!

Ich sitze gerade an einem Programm, dass wie im Titel bereits beschrieben die Wartezeiten von Patienten abfragen und am Ende auch den Mittelwert der Wartezeiten berechnen soll.

Dafür habe ich eine Klasse Time erstellt, die zum Beispiel Zeiten in Sekunden umrechnet, um sie addieren/subtrahieren zu können und auch wieder in die Form Stunde:Minute:Sekunde umwandelt.

In einem Hauptprogramm frage ich den Benutzer wann der Patient die Praxis betreten und verlassen hat, berechne daraus die Wartezeit, und sammle solange die Daten von Patienten in einer Liste bis der Benutzer signalisiert keine mehr eingeben zu müssen.

Problem 1: Die einzelnen Methoden in der Klasse Time funktionieren bis jetzt. Aber so wie ich das sehe habe ich noch nicht so ganz verstanden wie das mit dem Zugriff auf eine Klasse klappt. Ich verstehe nicht den Unterschied zwischen 2 underscores vor einem Methodennamen und 2 davor und 2 dahinter. Wahrscheinlich müsste ja meine Methode "__sum" in der Klasse Time private sein, weshalb beim Beenden der Patientenabfrage im Hauptprogramm die Fehlermeldung kommt.

Problem 2: Seit 2-3 Projekten schleicht es sich bei mir immer wieder ein, dass sich bei prints hinten dran ein "None" einschleicht. Woran kann das liegen?

Problem 3: Ich habe in der Vorlesung gelernt, dass wenn man einen Parameter beim Übergeben an eine Funktion vorher mit einem Sternchen ergänzt, alle gelisteten bzw gegebenen Werte benutzt werden.
Allerdings verstehe ich nicht wie ich in der folgenden Rechnung von der Funktion arithmeticMean in meinem Hauptprogramm wirklich alle Werte nutze. Das Sternchen darf ich dort ja nicht wieder eintragen.

Tut mir Leid für die Wall of Text, aber ich kann mich nie kurz fassen :?

Tipp: Falls ihr das Programm ausführt, müsst ihr mindestens die Zeiten für 2 Patienten eintragen, weil ich noch nicht implementiert habe, wie er reagiert, wenn er nicht genug Werte zum Berechnen des Mittelwerts hat.

Im Folgenden der Code und schon mal vielen Dank für eventuelle Tipps und Hilfestellungen!

Die Klasse Time:

Code: Alles auswählen

class Time:

    def __init__(self, hours=0, minutes=0, seconds=0):
        self.__hours = hours
        self.__minutes = minutes
        self.__seconds = seconds

    def __add__(self, time):
        seconds = self.__timeInSeconds() + time.__timeInSeconds()
        newTime = Time()
        newTime.__secondsInTime(seconds)
        return newTime

    def subtract(self, time):
        seconds = self.__timeInSeconds() - time.__timeInSeconds()
        newTime = Time()
        newTime.__secondsInTime(seconds)
        return newTime

    def __gt__(self, time):
        return self.__timeInSeconds() > time.__timeInSeconds()

    def __timeInSeconds(self):
        return 3600 * self.__hours + 60 * self.__minutes + self.__seconds

    def __secondsInTime(self, tIS):
        self.__hours = tIS // 3600
        self.__minutes = tIS % 3600
        self.__seconds = self.__minutes % 60
        self.__minutes = self.__minutes // 60

    def __sum(self, *time):
        summe = self.__timeInSeconds() + time.__timeinSeconds()
        newTime = Time()
        newTime.__secondsInTime(summe)
        return newTime

    def __str__(self):
        return str(self.__hours) + ":" + str(self.__minutes) + ":" + str(self.__seconds)

    
#main    
if __name__ == "__main__":

    t1 = Time(1, 10, 20)
    t2 = Time(1, 10, 20)
    t3 = Time(1, 10, 20)
    t4 = Time(1, 10, 20)
Das Hauptprogramm:

Code: Alles auswählen

from Time import Time

def erhalteDaten():
    patienten= []
    ende = 0
    while ende != 1:
        antwort = input(print("Möchten Sie Patienten der Liste hinzufügen? Ja oder Nein?"))
        if antwort == "Ja":

            betretenStunde = int(input(print("In welcher Stunde hat der Patient die Praxis betreten?")))
            betretenMinute = int(input(print("In welcher Minute?")))
            betretenSekunde = int(input(print("In welcher Sekunde?")))
            ankunft = Time(betretenStunde, betretenMinute, betretenSekunde)
                        
            verlassenStunde = int(input(print("In welcher Stunde hat der Patient die Praxis verlassen?")))
            verlassenMinute = int(input(print("In welcher Minute?")))
            verlassenSekunde = int(input(print("In welcher Sekunde?")))
            austreten = Time(verlassenStunde, verlassenMinute, verlassenSekunde)

            listValue = austreten.subtract(ankunft)
            patienten.append(listValue)
            
            
        else:
            ende += 1
    #print(patienten)
    print("Der Mittelwert aller Wartezeiten ist: ", arithmeticMean(patienten[0], patienten[1:]))

def arithmeticMean(value, *morevalues):
    summe = __sum(value, morevalues)
    middle = summe / len(patienten)

    return middle
                
           
            
        

erhalteDaten()
Didres
User
Beiträge: 5
Registriert: Donnerstag 29. Oktober 2015, 12:26

Okay, ich sehe gerade, dass ich auf jeden Fall im Hauptprogramm in der Funktion arithmeticMean die Methode __sum falsch aufgerufen habe.
Müsste sein:

Code: Alles auswählen

summe = value.__sum(morevalues)
oder nicht?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

1. Schau dir mal das dateime Modul an.

2. Die doppelten, fuehrenden Anfuehrungszeichen stehen nicht fuer "private" Attribute, sondern sind ein Mechanismus fuer Mehrfachvererbung.

Und damit kommen wir direkt zu deinem konkreten Problem: Ja, du rufst `sum` falsch auf. Der "richtige" Aufruf waere `value._Time__sum`, weil es zu Name Mangling kommt: https://docs.python.org/2/tutorial/clas ... references

Die Loesung? Lass die verfluchten __ weg. Und erst recht wenn du sie von ausserhalb der Klasse aufrufen willst, dann sind duerften sie naemlich _niemals_ private sein.

Edit: `__sum` ist uebrigens auch falsch implementiert. In `time` steckt eine Liste und kein einzelnes Argument.
BlackJack

@Didres: Die `Time`-Klasse kannst Du am besten gleich wieder vergessen und Dir mal das `datetime`-Modul in der Standardbibliothek anschauen. So etwas wird schon frei Haus geliefert. Das Rad muss man nicht neu erfinden.

Wenn Du nicht verstehst wofür zwei führende Unterstriche gut sind dann verwende die nicht. Leute die das verstanden haben verwenden die in aller Regel auch nicht. Zwei führende Unterstriche bedeutet *nicht* „private“ — das kennt Python schlicht nicht, auch wenn es immer wieder Leute gibt die von anderen Programmiersprachen kommen (insbesondere auch Dozenten in der Uni) die das irgendwie geistig nicht auf die Reihe bekommen das es nicht in jeder Sprache Zugriffsschutz für die Unterscheidung zwischen „private“ und „public“ gibt. Wenn man Attribute hat die nicht zur öffentlichen API gehören dann stellt man denen *einen* Unterstrich voran. Als Zeichen für den Leser das er da nur drangehen soll wenn er weiss was er tut und für Folgen selbst verantwortlich ist.

Führende und folgende doppelte Unterstriche haben eine gänzlich andere Bedeutung, das sind Namen die das Verhalten der Objekte zum Beispiel bei Operatoren festlegen, also für das Zusammenspiel zwischen den Objekten und der Sprache da sind. Die Python-Dokumentation nennt die auch „magic methods“ und da kann man auch nachlesen welche Bedeutung die haben und in welchem Zusammenhang die jeweils aufgerufen werden.

Wenn `print()` ein `None` ausgibt dann wurde der entsprechende Ausdruck zu einem `None` ausgewertet. Und das wird dann halt ausgegeben. Du musst an der Stelle schauen was die Ausdrücke als Ergebnis liefern und halt herausfinden warum das `None` ist obwohl Du anscheinend mit etwas anderem rechnest.

Bezüglich der Namensschreibeisen lohnt sich ein Blick in den Style Guide for Python Code.

Warum `__add__` aber dann `subtract()` anstelle von `__sub__`?

`__secondsInTime` würde sich als Klassenmethode öffentlich und unter dem Namen `from_seconds()` vielleicht ganz gut machen, dann könnte man auch einige der anderen Methoden deutlich kompakter schreiben. Beispielsweise:

Code: Alles auswählen

# ...
    def __add__(self, other):
        return Time.from_seconds(self.as_seconds() + other.as_seconds())
Der Argumentname `tIS` ist gruselig — Abkürzungen sollte man vermeiden solange sie nicht allgemein bekannt sind. Warum ist `__timeInSeconds` nicht ein öffentliches `as_seconds()`?

Ich bin nicht so in Python 3 drin, aber gibt es da nicht mittlerweile in der Standardbibliothek ein `statistics`-Modul mit einer `mean()`-Funktion? Bräuchte man also auch nicht selber schreiben.

Das mit dem Sternchen bei Argumenten bei Funktionssignaturen solltest Du bleiben lassen. Insbesondere wenn das dann dazu führt wenn der Code komplexer wird, in der Funktion/Methode wieder das Sternchen zum Einsatz kommt und am Ende vielleicht auch noch beim Aufruf immer ein Sternchen verwendet werden muss. Da kann man die ganzen Sternchen einfach weglassen und tatsächlich eine Liste als Argument übergeben. Das ist weniger komplex und man kann die API später auch noch um weitere Positionsargumente erweitern, was man sich mit einem ``*args`` verbaut. Es gibt Situationen in denen diese Sternchen-Magie in einer Funktionssignatur nützlich ist, die sind aber eher selten und man sollte sich das gut überlegen.

Die ``while``-Schleife im Hauptprogramm würde ich ohne die Variable `ende` schreiben. Einfach als ”Endlosschleife” die per ``break`` verlassen wird. *Wenn* schon ein Flag, dann bitte mit `True` und `False` und nicht mit ganzen Zahlen. Insbesondere das ``ende += 1`` ist verwirrend weil das nahelegt `ende` könnte beliebige ganzzahlige Werte annehmen und man sich erst die Definition und die Schleifenbedingung anschauen muss um zu erkennen das dem nicht so ist.

Der Code für die Abfrage einer Zeit steht da im Grunde zweimal. Das ist ein Zeichen dass man das *einmal* in einer Funktion schreiben sollte. Ich würde dem Benutzer auch nicht zumuten die Zeiten in drei Teilen eingeben zu müssen, sondern der `Time`-Klasse eine Klassenmethode `from_string()` oder `parse()` spendieren die sozusagen die Umkehrfunktion von `__str__()` ist. Bei `datetime` währe dass dann die `datetime.datetime.strptime()`-(Klassen-)Methode die man in der Eingabefunktion verwenden würde.

Fehlerbehandlung bei der Eingabe währe auch nicht schlecht. Man stelle sich vor der Benutzer gibt 9 Patienten ein und beim 10. vertippt er sich, das `int()` läuft schief und das ganze Programm bricht einfach ab.

In der Mittelwertfunktion steht `__sum()` — erstens gibt es diese Funktion nicht, und zweitens sollte die Methode von `Time` damit gemeint sein, dann macht es keinen Sinn die Methode als Implemetierungsdetail kennzeichnen zu wollen, denn dann ist sie ja offensichtlich dafür vorgesehen von aussen aufgerufen zu werden.

Allerdings verstehe ich nicht warum das überhaupt eine Methode auf `Time`-Objekten ist, denn diese API ist irgendwie doch sehr asymetrsich. Ausserdem gibt es ja bereits die `sum()`-Funktion die mit allem klarkommt was eine `__add__()`-Methode hat, also auch mit `Time`-Exemplaren.

Und an der Mittelwertfunktion sieht man auch den Unsinn von dem *-Argument, was unsinnigerweise dazu führt das Du eine Liste hast und das erste Element vom Rest trennst (durch eine Listenkopie) obwohl es doch viel sinnvoller wäre dort einfach *eine* Liste mit *allen* Werten zu erwarten.

Die Funktion sollte wenn man sie selber schreibt einfach so aussehen:

Code: Alles auswählen

def mean(values):
    return sum(values) / len(values)
Dann muss man `Time` nur noch eine `__div__`-Methode verpassen damit das Teilen durch einen Skalarwert funktioniert.

`middle` IMHO ist ein falscher Name und man muss auch nicht jeden Pups an einen Namen binden. Die Funktion ist eigentlich ein Einzeiler — ohne das sie dadurch unverständlich wird. Siehe oben.
Antworten