Code für eine einfache Aufgaben-Zeitplanung

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Sarkany
User
Beiträge: 8
Registriert: Dienstag 2. April 2019, 08:09
Wohnort: schönste Stadt am Rhein

ich habe für meine Firma ein Programm geschrieben das bestimmte Aufgaben in regelmäßigen Intervallen ausführt. Da das finden des Letzten Werktags eines Monats ein paar mehr Codezeilen wurden habe ich das Modul so weit erweitert das ich in meinem Hauptprogramm datetime nicht mehr importiere.
Über 4 einfache Befehle können Zeitpunkte in Minuten, Stunden, Tages, Wochen oder Monatsschritten gesetzt werden.
Jetzt() gibt entweder ein datetime-, date- oder time-Objekt zurück.
Zeitpunkt() gibt einen in der Zukunft oder Vergangenheit liegenden Zeitpunkt zrück.
nächstesMonEnde() gibt den letzten Tag des folgenden Monats zurück
nurWerktag() setzt das Datum auf einen Freitag, sollte ein Sams- oder Sonntag übergeben werden.
Die nurWerktag() Funktion ist aus der nächstetsMonEnde() entstanden da bestimmte Aufgaben am letzten Werktag des Monats erledigt werden müssen.

Alles in allem ist das kein Hexenwerk aber vielleicht für den Einen oder Anderen hilfreich oder ein Lösungsansatz.

Code: Alles auswählen

import datetime
""" infos zu datetime:
    https://docs.python.org/3.2/library/datetime.html#date-objects
"""
def Jetzt(rückgabe = "x"):
    if rückgabe.upper() in "DATUM":
        c_datum = datetime.date.today()
    elif rückgabe.upper() in "ZEIT":
        c_datum = datetime.datetime.now().time()
    elif rückgabe == "x":
        c_datum = datetime.datetime.now()
    else:
        hw = "Jetzt() kann mit einem optionalen Parameter aufgerufen wer"
        hw += "den. Ohne Parameter wird ein DateTime Objekt zurückgegebe"
        hw += "n, bei 'D' ein Date Objekt und bei 'Z' ein Time Objekt"
        print(hw)
        return
    return c_datum


def Zeitpunkt(c_datum,*x):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw = "Das Datum muß als datetime-Objekt übergeben werden, welches"
        hw += " über die Funktion Jetzt() erstellt werden kann"
        print(hw)
        return
    if len(x) == 0:
        hw = "Fkt. Aufruf mit mind. einem zweiten Parameter, welcher di"
        hw += "e Zeitspanne für die neue Zeit definiert. Möglich sind:"
        hw += "'monat=x', 'woche=x', 'tag=x', 'stunde=x', 'minute=x'."
        print(hw)
        return

    def Monatswechsel(c_dat,n_wert):
        n_ja,n_mo,n_ta = c_dat.year,c_dat.month,c_dat.day
        zahl = int(n_wert/abs(n_wert))
        n_wert = abs(n_wert)
        while n_wert > 0:
            n_mo += zahl
            if n_mo == 13 or n_mo == 0:
                n_ja += zahl
                n_mo = 1 if zahl > 0 else 12
            n_wert -= 1
        if n_ta > 28 and 1 < n_mo < 12:
            tmp_tg = (datetime.date(n_ja,n_mo+1,1
                                    )-datetime.timedelta(days=1)).day
            n_ta = tmp_tg if tmp_tg < n_ta else n_ta
        if type(c_dat) == datetime.datetime:
            n_hr,n_mi,n_sk = c_dat.hour,c_dat.minute,c_dat.second
            c_dat = datetime.datetime(n_ja,n_mo,n_ta,n_hr,n_mi,n_sk)
        else:
            c_dat = datetime.date(n_ja,n_mo,n_ta)
        return c_dat

    for part in x:
        part = part.replace(" ","")
        n_wert = int(part[part.find("=")+1:])
        if "monat" in part:
            c_datum = Monatswechsel(c_datum,n_wert)
        elif "woche" in part:
            c_datum = c_datum+datetime.timedelta(weeks=n_wert)
        elif "tag" in part:
            c_datum = c_datum+datetime.timedelta(days=n_wert)
        elif "stunde" in part:
            c_datum = c_datum+datetime.timedelta(hours=n_wert)
        elif "minute" in part:
            c_datum = c_datum+datetime.timedelta(minutes=n_wert)
        else:
            hw = "unbekannter Parameter. Mögliche Parameter sind\n"
            hw += "'monat=x', 'tag=x', 'stunde=x', 'minute=x'"
            print(hw)
    return c_datum
    

def nächstesMonEnde(c_datum):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw ="das Datum muß als datetime.datetime Objekt oder datet"
        hw +="ime.date Objekt übergeben werden!"
        print(hw)
        return
    n_mo = c_datum.month+2
    n_ja = c_datum.year
    if n_mo == 13:
        n_mo = 1
        n_ja += 1
    elif n_mo == 14:
        n_mo = 2
        n_ja += 1
    if type(c_datum) == datetime.datetime:
        n_hr,n_mi,n_sk = c_datum.hour,c_datum.minute,c_datum.second
        c_datum = datetime.datetime(n_ja,n_mo,1,n_hr,n_mi,n_sk
                                    )-datetime.timedelta(days=1)
    else:
        c_datum = datetime.date(n_ja,n_mo,1
                                )-datetime.timedelta(days=1)
    return c_datum


def nurWerktag(c_datum):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw ="das Datum muß als datetime.datetime Objekt oder datet"
        hw +="ime.date Objekt übergeben werden!"
        print(hw)
        return
    if c_datum.isoweekday() > 5:
        tage = c_datum.isoweekday() -7 + 2
        c_datum = c_datum-datetime.timedelta(days=tage)
    return c_datum


if __name__ == "__main__":
    # Datum und Zeit von heute als datetime-Obj:
    print("Datum + Zeit:",Jetzt())
    # nur Datum als Obj:
    print("nur Datum:",Jetzt("Datum"))
    # nur Zeit als Obj:
    print("nur Zeit:",Jetzt("Zeit"))
    d1 = Jetzt()
    print("nächstes Monatsende:",nächstesMonEnde(d1))
    print("nur Werktage:       ",nurWerktag(nächstesMonEnde(d1)))
    print("Zeitpunkt +3Monate: ",Zeitpunkt(d1,"monat=3"))
    print("Zeitpunkt -4Monate: ",Zeitpunkt(d1,"monat=-4"))
    print("Zeitpunkt -3Wochen: ",Zeitpunkt(d1,"wochen=-3"))
das Schönste am Programmieren ist, dass man mit Ausdauer und Hartnäckigkeit am Ende immer belohnt wird. :mrgreen:
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarkany: Ich habe da nur kurz drüber geschaut und das sieht auf den ersten Blick schon alles sehr gruselig aus. Die Namensschreibweisen, die kaputte API mit komischen Argumenten die entscheiden was eine Funktion dann tatsächlich tut, und das mit Tests die falsch sind, Argumente die Namen und Zahlen als Zeichenketten übergeben statt da Schlüsselwortargumente für zu verwenden, Ausgabe von Fehlertexten mit `print()` in Funktionen die nichts mit Benutzerinteraktion zu tun haben und dann implizite `None`\s als Rückgabe im Fehlerfall statt Ausnahmen, oder ungewollte Ausnahmen, Typprüfungen mit `type()`, ein paar mehr als bescheidene Namen, eine lokale Funktion die keine sein sollte, Zeichenkette als Kommentar, …
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Zeichenkettenliterale sind keine Kommentare. Kommentare kommen *vor* der/den Zeile(n) die sie kommentieren. Und bei einem Modul aus der Standardbibliothek macht es keinen Sinn in einem Kommentar den Link auf die Dokumentation zu setzen mit dem Hinweis das man da Informationen zu dem Modul findet. Das ist kein Mehrwert für den Leser.

Dokumentation zu Modulen kann man in Kommentaren verlinken, wenn die in Python Package Index einen ganz anderen Namen haben und die Verbindung nicht durch eine `requirements.txt` oder einem `pipenv`-Lockfile oder auf ähnliche Weise hergestellt werden kann, oder wenn das ein obskures Modul ist, das man gar nicht auf einem Standardweg installiert bekommt.

Die `Jetzt`-Funktion bekommt ein Argument und je nach Argument für einer von drei Unterschiedlichen Wege durch die Funktion genommen die jeweils einen anderen Datentyp als Ergebnis haben. Das ist also nicht *eine* Funktion, sondern *drei* Funktionen in einer gepackt, ohne das da ein Grund für ersichtlich ist.

Das Argument wird ausserdem falsch ausgewertet, denn auch 'a', 'M', 't', 'U', 'TU', oder 'at' liefern ein Datumsobjekt, so wie auch 'E', 'i', 'T', 'ei', oder 'IT' ein Zeitobjekt liefern würden. Und warum 'x' für ein komplettes `datetime`-Objekt stehen soll, ist mir auch nicht ersichtlich.

Wenn man das als drei unterschiedliche Funktionen schreibt, kann man sich zwei davon auch gleich komplett sparen, weil die einfach nur auf eine Umbenennung von statischen Methoden auf den jeweiligen Objekten hinaus laufen. Nur das ermitteln von nur der Zeit benötigt einen kleinen Zwischenschritt.

Die `Zeitpunkt()`-Funktion ist lang, voller kryptischer Namen, und unverständlich. Warum muss man da neben dem Datum zwingend etwas übergeben?

Das als Zeichenketten zu machen ist auch eine komische API. Du hast doch die `timedelta`-API als Vorbild, warum machst Du das dann anders? Bis auf Monate kannst Du doch auch alles 1:1 durchreichen.

`Zeitpunkt()` wird überflüssig wenn man sich `dateutil.relativedelta` ins Boot holt. Eine recht alte und viel benutzte Bibliothek. `pandas` verwendet die beispielsweise, das heisst der Code ist wirklich viel benutzt und getestet.

Das ermitteln des letzten Tages des nächsten Monats wird damit zum Einzeiler.

Code: Alles auswählen

#!/usr/bin/env python3
from datetime import date as Date, datetime as  DateTime, timedelta as TimeDelta
from dateutil.relativedelta import relativedelta


def get_current_time():
    return DateTime.now().time()


def get_next_month_end(date):
    return date + relativedelta(day=1, months=2, days=-1)


def ensure_workday(date, advancing=True):
    delta = TimeDelta(days=1 if advancing else -1)
    while not 0 <= date.weekday() <= 4:
        date += delta
    return date


def main():
    print('Datum + Zeit:', DateTime.now())
    print('nur Datum:', Date.today())
    print('nur Zeit:', get_current_time())
    
    date = DateTime(2019, 2, 5)
    print('nächstes Monatsende:', get_next_month_end(date))
    print(
        'nur Werktage:       ', ensure_workday(get_next_month_end(date), False)
    )
    print('Zeitpunkt +3 Monate:', date + relativedelta(months=3))
    print('Zeitpunkt -4 Monate:', date + relativedelta(months=-4))
    print('Zeitpunkt -3 Wochen:', date - relativedelta(weeks=3))


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sarkany
User
Beiträge: 8
Registriert: Dienstag 2. April 2019, 08:09
Wohnort: schönste Stadt am Rhein

Auaha, das ist eine Menge Haue!!

Was meinst du mit kaputter API?
Die Fkt. Zeitpunkt() wird bei der Verwendung von Schlüsselwortargumenten durch den Wegfall der Schleife klarer. Nur entfällt jetzt die Möglichkeit bei Übergabe von ungültigen Parametern einen Hinweis über den korrekten Aufruf der Fkt. zu platzieren, da die Ausnahme schon beim Aufruf der Fkt. entsteht. Ich befürchte, um dieses Problem zu lösen würde ich das Komplette Modul wohl ein eine Klasse packen müssen?

Übrigens ist mir nicht klar wo das Problem bei der print() Ausgabe sein soll. Werden nicht alle Exceptions über print() ausgegeben?

Was spricht gegen die Typprüfung?
Was spricht gegen eine Fkt. in einer Fkt.?
Davon abgesehen das die Typprüfung nicht i.O. sein soll, was wäre denn anstelle von None eine korrekte Rückgabe?

Code: Alles auswählen

import datetime
#    infos zu datetime:
#    https://docs.python.org/3.2/library/datetime.html#date-objects

def Jetzt(rückgabe = "x"):
    err_msg = "Jetzt() kann mit einem optionalen Parameter aufgerufen wer"
    err_msg += "den. Ohne Parameter wird ein DateTime Objekt zurückgegebe"
    err_msg += "n, bei 'D' ein Date Objekt und bei 'Z' ein Time Objekt"
    try:
        if rückgabe.upper() in "DATUM":
            c_datum = datetime.date.today()
        elif rückgabe.upper() in "ZEIT":
            c_datum = datetime.datetime.now().time()
        elif rückgabe == "x":
            c_datum = datetime.datetime.now()
        else:
            raise ValueError(err_msg)
            return
    except AttributeError:
        raise AttributeError(hw)
    return c_datum

def Zeitpunkt(c_datum,monat=0,woche=0,tag=0,stunde=0,minute=0):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw = "Das Datum muß als datetime-Objekt übergeben werden, welches"
        hw += " über die Funktion Jetzt() erstellt werden kann"
        print(hw)
        return

    if monat != 0:
        n_ja,n_mo,n_ta = c_datum.year,c_datum.month,c_datum.day
        zahl = int(monat/abs(monat))
        n_wert = abs(monat)
        while n_wert > 0:
            n_mo += zahl
            if n_mo == 13 or n_mo == 0:
                n_ja += zahl
                n_mo = 1 if zahl > 0 else 12
            n_wert -= 1
        if n_ta > 28 and 1 < n_mo < 12:
            tmp_tg = (datetime.date(n_ja,n_mo+1,1
                                    )-datetime.timedelta(days=1)).day
            n_ta = tmp_tg if tmp_tg < n_ta else n_ta
        if type(c_datum) == datetime.datetime:
            n_hr,n_mi,n_sk = c_datum.hour,c_datum.minute,c_datum.second
            c_datum = datetime.datetime(n_ja,n_mo,n_ta,n_hr,n_mi,n_sk)
        else:
            c_datum = datetime.date(n_ja,n_mo,n_ta)
    if woche > 0:
        c_datum = c_datum+datetime.timedelta(weeks=n_wert)
    if tag > 0:
        c_datum = c_datum+datetime.timedelta(days=n_wert)
    if stunde > 0:
        c_datum = c_datum+datetime.timedelta(hours=n_wert)
    if minute > 0:
        c_datum = c_datum+datetime.timedelta(minutes=n_wert)
#    if x:
#        hw = "unbekannter Parameter. Mögliche Parameter sind\n"
#        hw += "'monat=x', 'tag=x', 'stunde=x', 'minute=x'\n"
#        print(hw,err)
        
    return c_datum
    

def nächstesMonEnde(c_datum):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw ="das Datum muß als datetime.datetime Objekt oder datet"
        hw +="ime.date Objekt übergeben werden!"
        print(hw)
        return
    n_mo = c_datum.month+2
    n_ja = c_datum.year
    if n_mo == 13:
        n_mo = 1
        n_ja += 1
    elif n_mo == 14:
        n_mo = 2
        n_ja += 1
    if type(c_datum) == datetime.datetime:
        n_hr,n_mi,n_sk = c_datum.hour,c_datum.minute,c_datum.second
        c_datum = datetime.datetime(n_ja,n_mo,1,n_hr,n_mi,n_sk
                                    )-datetime.timedelta(days=1)
    else:
        c_datum = datetime.date(n_ja,n_mo,1
                                )-datetime.timedelta(days=1)
    return c_datum


def nurWerktag(c_datum):
    if type(c_datum) not in [datetime.datetime,datetime.date]:
        hw ="das Datum muß als datetime.datetime Objekt oder datet"
        hw +="ime.date Objekt übergeben werden!"
        print(hw)
        return
    if c_datum.isoweekday() > 5:
        tage = c_datum.isoweekday() -7 + 2
        c_datum = c_datum-datetime.timedelta(days=tage)
    return c_datum


# Datum und Zeit von heute als Datum-Klasse:
print("Datum + Zeit:",Jetzt())
# nur Datum als Klasse:
print("nur Datum:",Jetzt("Datum"))
# nur Zeit als Klasse:
print("nur Zeit:",Jetzt("Zeit"))
d1 = Jetzt()
print("nächstes Monatsende:",nächstesMonEnde(d1))
print("nur Werktage:       ",nurWerktag(nächstesMonEnde(d1)))
print("Zeitpunkt +3Monate: ",Zeitpunkt(d1,monat=3))
print("Zeitpunkt -4Monate: ",Zeitpunkt(d1,monat=-4))
print("Zeitpunkt -3Wochen: ",Zeitpunkt("d1",woche=-3))
das Schönste am Programmieren ist, dass man mit Ausdauer und Hartnäckigkeit am Ende immer belohnt wird. :mrgreen:
Sarkany
User
Beiträge: 8
Registriert: Dienstag 2. April 2019, 08:09
Wohnort: schönste Stadt am Rhein

oh, deine zweite Antw. hatte ich noch gar nicht gesehen...
das Schönste am Programmieren ist, dass man mit Ausdauer und Hartnäckigkeit am Ende immer belohnt wird. :mrgreen:
Sarkany
User
Beiträge: 8
Registriert: Dienstag 2. April 2019, 08:09
Wohnort: schönste Stadt am Rhein

whow, da ist aber wenig Code übrig geblieben. Aber das ist die Stärke von Python, dass es so unglaublich viele und leistungsfähige Module gibt.
Das Modul dateutil hatte ich bei meiner Suche nach Datum- Zeitfunktionen nicht gefunden.

Vielen Dank, auch wenn ich weniger Kritik erwartet hatte. Solltest Du noch die Muße finden auf meine Fragen aus der vorherigen Nachricht einzugehen wäre ich nochmals Dankbar.

Schönes Restwochenende...
das Schönste am Programmieren ist, dass man mit Ausdauer und Hartnäckigkeit am Ende immer belohnt wird. :mrgreen:
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Eine Fehlermeldung zu Printen statt eine Exception zu werfen ist das falsche Vorgehen, weil so einfach weiter gemacht wird mit “None” als Rückgabe, womit dann später erst hart zu findende folgefehler entstehen.

Und eine Funktion die ihren Rückgabewert abhängig von Argumenten ändert ist überraschend. Manchmal nicht zu vermeiden, aber du könntest genauso gut drei alleine stehende Funktionen nehmen, und gut ist.

Desweiteren machen deine Funktionen viel zu viel. Es ist deutlich besser Code aus zusammensetzbaren Teilen zu schreiben. Also zb eine Funktion “letzter_tag_des_monats”, dann “nächster_monat” und dann die etwas rumpelig benamte “arbeitstag_vorher”. Aus den dreien kannst du dann deine Funktionalität zusammensetzen, und das ganze wird durchschaubarer, und auch nuntzbarer für andere Zwecke.
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

Sarkany hat geschrieben: Sonntag 28. April 2019, 12:28 Vielen Dank, auch wenn ich weniger Kritik erwartet hatte. Solltest Du noch die Muße finden auf meine Fragen aus der vorherigen Nachricht einzugehen wäre ich nochmals Dankbar.
Ich picke mal einen Punkt heraus: Typen prüft man in Python mit 'isinstance'. Da Python "Duck-Typing" unterstützt, sind Typprüfung aber sehr selten: Wenn du die Länge eines Objekts mit 'len()' bestimmen willst, wird machst du das einfach. Ob das klappt, hängt einzig davon ob, ob das Objekt '__len__' sinnvoll implementiert, unabhängig davon, ob das jetzt eine Liste, ein String oder sonst was ist. Wenn das nicht klappt, gibt es halt eine 'Exception'. Siehe dazu auch: https://docs.python.org/3/glossary.html ... uck-typing ; praktischerweise geht es im Abschnitt direkt darunter um ein weiteres Prinzip, das in dem Kontext relevant ist (EAFP).
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarkany: In Ausnahmen oder Fehlerausgaben sollte gar nicht stehen wie man die Funktion aufruft, nur das sie falsch aufgerufen wurde. Für die Details gibt es die Dokumentation wo drin steht was die Funktion macht, welche Argumente sie erwartet, und was deren Wertebereich ist.

Wenn man `Zeitpunkt()` mit ungültigen Argumenten aufruft, im Sinne von ein anderes Schlüsselwort als die, die in der Signatur stehen, dann bekommt man von Python selbst eine Ausnahne. Konkret einen `TypeError` mit dem Hinweis das die Funktion ein unerwartetes Schlüsselwort-Argument übergeben bekommen hat.

Code: Alles auswählen

In [9]: def f(a=0, b=0):
   ...:     pass
   ...: 
   ...: 

In [10]: f(x=42)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-091b1c3a9caf> in <module>()
----> 1 f(x=42)

TypeError: f() got an unexpected keyword argument 'x'
Gegen Typprüfung spricht, dass das der Philosophie vom „duck typing“ widerspricht. Es ist in Python in aller Regel nicht wichtig welchen Typ ein Objekt hat, sondern wie es sich verhält. Du schliesst ohne guten Grund Objekte aus, die sich wie `datetime` oder `date` Objekte verhalten, aber eben nicht genau von einem dieser beiden Typen sind. Wenn einem konkrete Datentypen wichtig sind, sollte man eine statisch typisierte Programmiersprache verwenden. Oder in Python 3 die Typannotationen und ein Programm das die dann überprüft.

Ein Beispiel wäre eventuell `mxDateTime` als Alternative für das `datetime`-Modul aus der Standardbibliothek. (Ich weiss nicht wie aktuell oder nah dran an `datetime` das `mxDateTime`-Modul noch ist.)

Gegen Funktionen in Funktionen spricht, das man die nicht einzeln testen kann. Man kann lokale Funktionen verwenden wenn die sehr einfach, aber speziell sind, meistens als ``lambda``-Ausdruck, oder wenn man tatsächlich ein „closure“ benötigt. Beides ist bei `Monatswechsel()` nicht gegeben. Man kann das aber trotzdem als eigene Funktion schreiben und muss den Inhalt nicht ”inline” haben. Mit der gleichen Begründung wie bei der lokalen Funktion: Es ist so nicht einzeln testbar.

Korrekte Rückgabe ist immer ein gültiges Ergebnis – und falls das nicht möglich ist eine Ausnahme auslösen. Ausnahmen wurden erfunden um spezielle Rückgabewerte für Fehlerfälle los zu werden, so dass man beim Aufrufer nicht mehr jeden Aufruf explizit prüfen muss, ohne das durch die nicht vorhandene Prüfung der Fehler ignoriert wird und das Programm mit einem falschen Wert einfach weiter arbeitet. Auf der anderen Seite kann man auf die Ausnahme prüfen, und das nicht nur direkt beim/nach dem Aufruf, sondern irgendwo in der Aufrufhierarchie, wo das sinnvoll ist.

Ausnahmen werden nur ausgegeben wenn die Ausnahme auch bis nach ganz oben durchschlägt. Nicht wenn die Ausnaheme vorher irgendwo behandelt wird. Deine Kombination aus `print()` und Rückgabe von `None` macht *immer* eine Ausgabe, auch wenn der Aufrufer danach auf `None` prüft und irgend etwas sinnvolles machen kann, so dass der Aufruf am Ende doch kein Fehler war.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sarkany
User
Beiträge: 8
Registriert: Dienstag 2. April 2019, 08:09
Wohnort: schönste Stadt am Rhein

Das "duck typing" Konzept finde ich überzeugend. Insbesondere unter dem Aspekt das ein Date oder Time Objekt auch mit einem anderen Modul als datetime erstellt sein könnte.
Obwohl ich es nachvollziehbar finde, das eine Funktion die ihren Rückgabewert abhängig von Argumenten ändert überraschend ist, tue ich mich bei der Umstellung auf 3 Funktionen innerlich ein wenig schwerer. Mein Ansatz war das ich eine Funktion wollte die mir abhängig von der Intervall Einstellung der Aufgabe ein Datum, eine Zeit oder ein Datum-Zeit Wert gibt. Wenn ich hingegen die 3 Einzeiler "Heute(), Zeit() und HeuteZeit()" mit der Jetzt() Funktion vergleiche sind die Ersteren ungleich klarer und simpler. Nichts desto trotz habe ich die Jetzt() belassen und die Typprüfung durch duck typing ersetzt. Obwohl ich weiß das es dafür Kritik gibt konnte ich mir eine Erläuterung in der Fehlermeldung nicht verkneifen.

Auch wenn es für die Aufgaben die ich löse bereits Module gibt, erstens kenne ich nicht jedes Modul und zweitens geht es mir auch um das Sammeln von Erfahrung und das Finden von klaren und effektiven Lösungen.

Vielen Dank an alle. Ich habe hilfreiche Informationen erhalten und dazu gelernt. :P

Code: Alles auswählen

import datetime

def prüfeDatum(datum):
    try:
        dummy = datum.day
    except AttributeError:
        err_txt = "Ein Datum, Zeit oder Datum-Zeit Objekt muß übergeben we"
        err_txt += "rden. Erstellung über Heute() od. Zeit() möglich."
        raise AttributeError(err_txt)


def Heute():
    return datetime.date.today()


def Zeit():
    return datetime.datetime.now().time()


def HeuteZeit():
    return datetime.datetime.now()


def Jetzt(rückgabe = " "):
    err_txt = "Jetzt() kann mit einem optionalen Parameter aufgerufen wer"
    err_txt += "den. Ohne Parameter wird ein DateTime Objekt zurückgegebe"
    err_txt += "n, bei 'D' ein Date Objekt und bei 'Z' ein Time Objekt"
    try:
        if rückgabe.upper() in "DATUM":
            datum_o = datetime.date.today()
        elif rückgabe.upper() in "ZEIT":
            datum_o = datetime.datetime.now().time()
        elif rückgabe == " ":
            datum_o = datetime.datetime.now()
        else:
            raise ValueError(err_txt)
    except AttributeError:
        raise AttributeError(err_txt)
    return datum_o


def Zeitpunkt(datum_o,monat=0,woche=0,tag=0,stunde=0,minute=0):
    prüfeDatum(datum_o)
    if monat != 0:
        n_ja,n_mo,n_ta = datum_o.year,datum_o.month,datum_o.day
        zahl = int(monat/abs(monat))
        n_wert = abs(monat)
        for i in range(n_wert):
            n_mo += zahl
            if n_mo == 13 or n_mo == 0:
                n_ja += zahl
                n_mo = 1 if zahl > 0 else 12
        if n_ta > 28 and 1 < n_mo < 12:
            tmp_tg = (datetime.date(n_ja,n_mo+1,1
                                    )-datetime.timedelta(days=1)).day
            n_ta = tmp_tg if tmp_tg < n_ta else n_ta
        datum_o = _neueZeit(datum_o,n_ja,n_mo,n_ta)
    if woche != 0:
        datum_o = datum_o+datetime.timedelta(weeks=woche)
    if tag != 0:
        datum_o = datum_o+datetime.timedelta(days=tag)
    if stunde != 0:
        datum_o = datum_o+datetime.timedelta(hours=stunde)
    if minute != 0:
        datum_o = datum_o+datetime.timedelta(minutes=minute)
    return datum_o
    

def nächstesMonEnde(datum_o):
    prüfeDatum(datum_o)
    n_mo = datum_o.month+1
    n_ja = datum_o.year
    if n_mo == 13:
        n_mo = 1
        n_ja += 1
    n_tg = (datetime.date(n_ja,n_mo+1,1)-datetime.timedelta(days=1)).day
    datum_o = _neueZeit(datum_o,n_ja,n_mo,n_tg)
    return datum_o


def _neueZeit(c_dat,jah,mon,tag):
    try:
        hr,mi,sk = c_dat.hour,c_dat.minute,c_dat.second
        c_dat = datetime.datetime(jah,mon,tag,hr,mi,sk)
    except AttributeError:
        c_dat = datetime.date(jah,mon,tag)
    return c_dat


def nurWerktag(datum_o):
    prüfeDatum(datum_o)
    while datum_o.isoweekday() > 5:
        datum_o = datum_o-datetime.timedelta(days=1)
    return datum_o

if __name__ == "__main__":
    # Datum und Zeit von heute als Datum-Objekt:
    print("Datum + Zeit:",Jetzt())
    print("nur Datum:",Jetzt("Datum"))
    print("nur Zeit:",Jetzt("Zeit"))
    d1 = Jetzt()
    print("nächstes Monatsende:",nächstesMonEnde(d1))
    d1 = Jetzt("Datum")
    print("nur Werktage:       ",nurWerktag(nächstesMonEnde(d1)))
    print("Zeitpunkt +3Monate ",Zeitpunkt(d1,monat=3))
    print("Zeitpunkt +3Monate -2Tage: ",Zeitpunkt(d1,monat=3,tag=-2))
    print("Zeitpunkt -5Monate: ",Zeitpunkt(d1,monat=-5))
    print("Zeitpunkt -3Wochen: ",Zeitpunkt(d1,woche=-3))
das Schönste am Programmieren ist, dass man mit Ausdauer und Hartnäckigkeit am Ende immer belohnt wird. :mrgreen:
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sarkany: die Funktionsnamen halten sich nicht an die Namenskonvention, auch sollten sie Tätigkeiten beschreiben. Was sollen eigentlich die Suffixe oder Präfixe wie c_, _o, n_, auch sind Variablennamen mit zwei Buchstaben zu kurz. ja? ta? trara? Was soll das heißen?
Wer kommt eigentlich auf die bescheuerte Idee, Texte mitten in einem Wort umzubrechen?
› _neueZeit‹ kann man einfach mit replace implementieren, ohne das falsch aussehende AttributeError zu benutzen.
Die Funktionen sind zum Teil recht umständlich.
Dass ›Jetzt‹ nicht das prüft, was Du behauptest, hat Dir __blackjack__ ja schon geschrieben. Der AttributeError sollte wohl ein TypeError sein.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also Deine Namen machen mich fertig. Die halten sich nicht mal einheitlich nicht an die Namenskoventionen und bei den lokalen Namen sind echt schlechte dabei. Was sollen diese kryptischen Abkürzungen und Prä- und Suffixe? Warum heissen die gleichen Sachen in verschiedenen Funktionen unterschiedlich?

Die `err_txt` Zusammenstüchelungen mit ``+=`` sind überflüssig. Der Compiler fasst Zeichenkettenliterale die nur durch Whitespace-Zeichen getrennt sind, zusammen. Das muss man nicht zur Laufzeit mit Code machen.

Weder in Namen noch in Ausnahmetexten haben Zeichen ausserhalb von ASCII etwas zu suchen. Sonst kann es passieren das Ausnahmen bei der Ausgabe zu einer Ausnahme führen weil der ”Empfänger” die Zeichen nicht versteht.

Umlaute in Namen machen auch bei diversen Werkzeugen Probleme – vom Syntaxhighlighting in Editoren (oder hier im Forum) bis zu statischen Analysewerkzeugen. Ausserdem ist Deutsch keine gute Idee. Das führt früher oder später fast immer zu Problemen das man englische Namen und deutsche Namen mit der gleichen Bedeutung aber für verschiedene Bedeutungen der Werte bekommt, oder irgend etwas komisch oder falsch übersetzt und damit unnötig Verwirrung stiftet. Es gibt auch einige Idiome die sich bei der Namensgebungen eingebürgert haben, von denen man echt nicht die Übersetzung lesen möchte. Im besten Fall weil es komisch aussieht, im schlechteren Fall weil man nach der Übersetzung das Idiom nicht mehr erkennt, und damit der Code unverständlicher ist, weil Information verloren gegangen ist.

`Heute` und `HeuteZeit` kann man einfacher schreiben:

Code: Alles auswählen

Heute = datetime.date.today
HeuteZeit = datetime.datetime.now
Wobei `HeuteZeit` eigentlich `Jetzt` heissen müsste, wenn man `now()` den unbedingt ins Deutsche übersetzen muss.

In `Jetzt()` hast Du immer noch die kaputten Tests. Du willst da wirklich nicht mit ``in`` testen. Was im `err_txt` steht gehört IMHO in die Dokumentation und nicht in die Ausnahme.

Was soll der `_o`-Suffux bei `datum_o` bedeuten? Und – ein Problem bei dieser kaputten API – warum heisst ein `datetime.time`-Objekt `datum_o`? Das ist kein Datum!

Ich verstehe auch nicht warum Du diese Funktion brauchst? Die gehört hier nicht her. Da wo Du die Funktion aufrufst, musst Du ja entscheiden mit welchem Argument Du das tust. Da kannst Du genau so gut entscheiden welche der drei Einzelfunktionen Du aufrufst. Mich würde auch ein Aufrufer interessieren der tatsächlich mit allen drei Varianten weitermachen könnte ohne dann noch weitere Unterscheidungen machen zu müssen, weil die Schnittmenge an gemeinsamen Verhalten zwischen `datetime.time` und den beiden anderen Typen ja doch eher klein ist.

Kannst Du mal bitte einen Aufruf zeigen wo der ``except AttributeError:``-Zweig ausgeführt wird‽ Für mich sieht das nach Code aus der nie erreicht werden kann.

Die Argumentnamen bei `Zeitpunkt()` sind nicht ganz richtig. Die sollte man in der Mehrzahl benennen. Wenn man das macht, kann man `n_ja`, `n_mo`, `n_ta` auch sinnvoll benennen.

Die Bestimmung von `zahl` finde ich ein bisschen undurchsichtig. Das kann man auch einfacher und deutlicher schreiben. Und der Name `zahl` ist auch sehr nichtssagend. Ich bin mit `richtung` zwar nicht so ganz zufrieden, aber besser als `zahl` fände ich es schon.

Die `_neueZeit()`-Funktion ist überflüssig. Sowohl `datetime.date` als auch `datetime.datetime` haben eine `replace()`-Methode.

Am Ende der `Zeitpunkt()`-Funktion lässt sich ganz viel wegstreichen, nämlich die ganzen unnötigen Tests ob die Einzelkomponenten 0 sind. Da addiert man einfach *ein* `timedelta` mit allen Komponenten und gut ist:

Code: Alles auswählen

def Zeitpunkt(datum, monate=0, wochen=0, tage=0, stunden=0, minuten=0):
    if monate != 0:
        jahr, monat, tag = datum.year, datum.month, datum.day
        richtung = -1 if monate < 0 else 1
        for _ in range(abs(monate)):
            monat += richtung
            if monat in [13, 0]:
                jahr += richtung
                monat = 1 if richtung > 0 else 12
        if tag > 28 and 1 < monat < 12:
            tmp_tag = (
                datetime.date(jahr, monat + 1, 1) - datetime.timedelta(days=1)
            ).day
            tag = min(tmp_tag, tag)
        datum = datum.replace(jahr, monat, tag)

    return datum + datetime.timedelta(
        weeks=wochen, days=tage, hours=stunden, minutes=minuten
    )
Wenn man so etwas selbst programmiert, sollte man Unit-Tests für die interessanten Fälle schreiben. Also zumindest für die Monate, denn der Rest kommt ja aus dem `datetime`-Modul.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten