Alter berechnen

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
snowflake
User
Beiträge: 93
Registriert: Mittwoch 9. November 2016, 15:46

Hallo zusammen,

ich brauche ein Skript zur Berechnung des Alters in Jahren und habe mir da was zusammen gebastelt. Die Funktion ist gewährleistet. Kann man das Skript irgendwie vereinfachen?

Code: Alles auswählen

import datetime, sys

def alterberechnen(gdatum):

    try: # Umwandeln und gleichzeitige Überprüfung, ob ein Geburtsdatum vorliegt

        gdatum = gdatum.split(".")
        geburtsdatum = datetime.date(int(gdatum[2]), int(gdatum[1]), int(gdatum[0]))

    except:

        return 0 # Fehler beim Umwandeln des Strings in ein Datum

    heute = datetime.date.today()

    if int(heute.year-geburtsdatum.year)<=0:
        return 0 # Datum liegt in der Zukunft oder heute
    
    jahr = heute.year - geburtsdatum.year

    if heute.month<geburtsdatum.month:
        jahr = jahr - 1
        return jahr
    
    elif heute.month > geburtsdatum.month:
        return jahr
    
    elif heute.month == geburtsdatum.month:

        if heute.day > geburtsdatum.day:
            return jahr
        
        elif heute.day < geburtsdatum.day:
            jahr = jahr -1
            return jahr
        
        elif heute.day == geburtsdatum.day:
            return jahr

if __name__ == "__main__":

    while True:

        try:

            geburtsdatum = input("Bitte geben Sie das Geburtsdatum ein (dd.mm.yyyy):")
            print(alterberechnen(geburtsdatum))
            
        except KeyboardInterrupt:
            
            print("Anwendung durch Benutzer abgebrochen...")
            sys.exit()
Für Rückmeldungen bin ich sehr dankbar.

snowflake
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

dieser Thread https://stackoverflow.com/questions/221 ... -in-python bei Stack Overflow zeigt eine Reihe kürzerer Lösungen.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snowflake: `sys.exit()` kann und sollte man sich sparen. Die Anweisung ist unnötig weil das Programm an der Stelle sowieso ganz regulär ended wenn man es einfach bis zum Ende weiterlaufen lässt. Die Funktion macht, wenn überhaupt, nur Sinn wenn man nicht mit dem Exitstatus 0 beenden will. Ansonsten ist das oft ein „code smell“, dass man sich nicht genügend Gedanken über den Programmfluss gemacht hat und eine Abkürzung nehmen musste.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm sollte also nicht direkt unter dem ``if __name__ …`` stehen, sondern in einer Funktion. Denn sonst ist die Variable `geburtsdatum` auf Modulebene bekannt, was nicht sein sollte.

Das Argument von `alterberechnen()` (oder besser `alter_berechnen()`) ist kryptisch abgekürzt. Das sollte `geburtsdatum` heissen, damit der Leser nicht raten muss was das `g` in `gdatum` bedeuten soll.

Die Funktion sollte auch gleich das Datum bekommen und nicht eine Zeichenkette umwandeln *und* das Alter in Jahren berechnen. Der Kommentar beim ``try`` ist irreführend, denn da wird ja nirgends geprüft ob ein Geburtsdatum vorliegt.

Statt `split()` würde ich die `strptime()`-Methode verwenden.

Ganz wichtig: Keine ”nackten” ``except:``-Blöcke, ausser wenn da ein (nacktes) ``raise`` drin steht oder zumindest die komplette Ausnahme inklusive Traceback irgendwo gespeichert/protokolliert wird. Das sind sonst schwarze Löcher für Fehler oder Probleme die man dann nur sehr schwer findet. Sinnvolle Fehlerbehandlung behandelt nur die Ausnahmen die man an der Stelle erwartet. Wenn eine unerwartete Ausnahme auftritt, kann man sich ja schliesslich gar nicht sicher sein, das die Behandlung die dort im ``except``-Block steht, auch wirklich Sinn macht, oder ob man die Ausnahme nicht anders behandeln müsste, und in der Regel auch an anderer Stelle. In vorliegenden Programm macht eigentlich nur der `ValueError` Sinn, der beim Umwandeln der Zeichenkette in ein Datumsobjekt auftreten kann.

Wenn das Datum nicht umgewandelt werden kann, dann sollte die Funktion auch nicht 0 zurück geben wenn man die Umwandlung in der Funktion lassen würde. Der Aufrufer der Funktion sollte keine besonderen Fehlerwerte testen müssen, dafür gibt es Ausnahmen! Wobei hier noch erschwerend dazu kommt, das 0 gar kein besonderer Fehlerwert ist, sondern ein ganz regulärer Wert sein kann und der Aufrufer so gar nicht unterscheiden kann zwischen einer tatsächlichen 0 für eine gültige Eingabe und einer 0 für eine ungültige Eingabe.

Das berechnen der Jahresdifferenz zwischen Heute und dem Geburtsdatum steht zweimal im Code. Das `int()` bei der einen Berechnung macht wenig Sinn weil die Jahreszahlen bereits ganze Zahlen sind und beim abziehen auch wieder eine ganze Zahl heraus kommt.

Der Name `jahr` ist unpassend für etwas das kein Jahr ist, sondern eine Anzahl von Jahren.

``elif`` als letzter Zweig ist in der Regel ein Warnzeichen. Das kann okay sein, das kann aber auch bedeuten, dass man nicht alle möglichen Fälle abgedeckt hat, denn dann hätte man ja den letzten Fall mit einem ``else`` abhandeln können. Es ist gute Praxis wenn so ``if``-Konstrukt in einem ``elif`` endet, entweder zu dokumentieren warum das okay ist welche Fälle da nicht behandelt werden (sofern das nicht offensichtlich ist), oder aber ein ``else:`` mit einem ``assert False`` anzuhängen, damit man mitbekommt wenn der Fall den man nicht erwartet vielleicht doch mal auftritt. Das erleichtert die Fehlersuche ungemein. In Deinem Code würde die Codestruktur beispielsweise erlauben das die Funktion ganz bis zum Ende durchläuft und keine der expliziten ``return``-Anweisungen ausgeführt wird. Das sollten die Werte verhindern, aber wirklich abgesichert ist der Code dagegen nicht.

Allerdings kann man die jeweils beiden letzten ``elif``-Zweige bei Deinem Code problemlos in ``else``-Zweige umwandeln, denn die Bedingungen decken ja den gesamten Wertebereich ab und die Bedingungen in den beiden letzten ``elif``\s sind redundant und damit überflüssig.

Zwischenstand:

Code: Alles auswählen

    if jahre <= 0:
        return 0  # Datum liegt in der Zukunft oder heute.
    elif heute.month < geburtsdatum.month:
        return jahre - 1
    elif heute.month > geburtsdatum.month:
        return jahre
    else:
        if heute.day > geburtsdatum.day:
            return jahre
        elif heute.day < geburtsdatum.day:
            return jahre - 1
        else:
            return jahre
Das innere ``if``/``elif``/``else``-Konstrukt macht in zwei Zweigen das gleiche, das kann man so zusammenfassen bzw. Umstellen das man diese beiden Zweige zusammenfasst und somit nur ein ``if``/``else`` übrig bleibt:

Code: Alles auswählen

    if jahre <= 0:
        return 0  # Datum liegt in der Zukunft oder heute.
    elif heute.month < geburtsdatum.month:
        return jahre - 1
    elif heute.month > geburtsdatum.month:
        return jahre
    else:
        if heute.day < geburtsdatum.day:
            return jahre - 1
        else:
            return jahre
Wenn man nun das ``return jahre`` hinter dieses ganze Konstrukt schreibt und in den Zweigen die etwas anderes als `jahre` unverändert zurückgeben die Variable `jahre` entsprechend ändert, kann man die Zweige mit ``return jahre`` komplett weg lassen:

Code: Alles auswählen

    if jahre <= 0:
        jahre = 0  # Datum liegt in der Zukunft oder heute.
    elif heute.month < geburtsdatum.month:
        jahre -= 1
    elif heute.month == geburtsdatum.month:
        if heute.day < geburtsdatum.day:
            jahre -= 1

    return jahre
Die Bedinung vom letzten ``elif`` und dem ``if`` darin kann man zusammenfassen:

Code: Alles auswählen

    jahre = heute.year - geburtsdatum.year
    if jahre <= 0:
        jahre = 0  # Datum liegt in der Zukunft oder heute.
    elif heute.month < geburtsdatum.month:
        jahre -= 1
    elif heute.month == geburtsdatum.month and heute.day < geburtsdatum.day:
        jahre -= 1
Und nun kann man noch die beiden letzten ``elif``-Zweige zusammenfassen:

Code: Alles auswählen

    if jahre <= 0:
        jahre = 0  # Datum liegt in der Zukunft oder heute.
    elif (
        heute.month < geburtsdatum.month
        or heute.month == geburtsdatum.month and heute.day < geburtsdatum.day
    ):
        jahre -= 1
Das erste ``if`` könnte man noch mit Hilfe von `max()` beseitigen. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from datetime import date as Date, datetime as DateTime


def alter_berechnen(geburtsdatum):
    heute = Date.today()
    jahre = heute.year - geburtsdatum.year
    if (
        heute.month < geburtsdatum.month
        or heute.month == geburtsdatum.month and heute.day < geburtsdatum.day
    ):
        jahre -= 1
    
    return max(0, jahre)


def main():
    while True:
        try:
            answer = input('Bitte geben Sie das Geburtsdatum ein (dd.mm.yyyy):')
            try:
                geburtsdatum = DateTime.strptime(answer, '%d.%m.%Y').date()
            except ValueError:
                print('Die Eingabe ist kein gütiges Datum!')
            else:
                print(alter_berechnen(geburtsdatum))
        except KeyboardInterrupt:
            print('Anwendung durch Benutzer abgebrochen...')


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
snowflake
User
Beiträge: 93
Registriert: Mittwoch 9. November 2016, 15:46

Guten Abend zusammen,

vielen Dank für die Rückmeldungen.

@noisefloor: Die Lösungen in Stack Overflow sind in der Tat viel kürzer und teilweise auch besser nachvollziehbar.

@__blackjack__: Oh je, wenn ich mir meine ganzen Fehler ansehe, habe ich noch ganz schön viel zu lernen.
Deine Rückmeldung ist sehr detailliert und gut nachvollziehbar. Wenn man Deine Lösung mit meiner vergleicht stellt man sehr leicht fest, dass Dein Skript wesentlich besser zu lesen und nachzuvollziehen ist. So soll es ja auch eigentlich sein. Viele Dinge sind mir jetzt klar geworden. Dafür möchte ich Dir nochmals danken.

Wünsche Euch einen schönen Abend.

snowflake
Antworten