Kommandos inkommensurabel

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.
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

@hcshm: Ich würde für die möglicherweise fehlerträchtige Datumsumwandlung einen separaten Konverter bevorzugen, der als eigenständige Funktion implementiert ist und dadurch auch für andere Stellen im Programm wiederverwendbar bleibt.

Code: Alles auswählen

def convert_to_date(some_input):
    ...
    return date

Zudem ist so eine Funktion auch separat testbar.

Bei der Ausführung dieser Funktion kannst Du dann bestimmen, was im Fehlerfall passiert und wie darauf ggf. reagiert werden soll (ignorieren, loggen, oder was auch immer).

Übergibt man die korrekt konvertierten Daten nun an die neu angelegte Instanz, so bleibt der Code sehr aufgeräumt und man arbeitet immer mit definierten einheitlichen Datentypen:

Code: Alles auswählen

class Person:
    def __init__(self, name, geburtstag):
        self.name = name
        self.geburtstag = geburtstag

Das ist idealerweise schon alles.

Auch könnte man die Daten bereits beim Import in das korrekte Format überführen, wozu ein Konverter ebenfalls von Vorteil ist. Denn z.B. numpy oder auch pandas bieten entsprechende hooks für genau diesen Anwendungsfall.
Sirius3
User
Beiträge: 18264
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast wirklich eine lustige Definition von Robustheit. Robust ist bei mir nicht, dass das Programm rät, was denn der Nutzer gemeint haben könnte, statt bei einem falschen Format einfach abzubrechen und den Nutzer die Chance zu geben, den Fehler zu korrigieren.

Dein Code hat nämlich einen riesigen Bug, den Du dank Deiner Robustheit einfach niederbügelst.

Code: Alles auswählen

n [2]: parse('3.2.2021')
Out[2]: datetime.datetime(2021, 3, 2, 0, 0)

Eine Name ist kein bytes-Objekt. Denn alle Strings werden intern als Strings behandelt. Die Logik ist auch verquer, warum ein String immer in ein utf8-Codiertes Bytes-Objekt umgewandelt wird, mit dem man nichts sinnvolles anfangen kann, und gleichzeitig ein latin1-Codierter Name als bytes-Objekt ohne Probleme durch Deine tolle Robustheitsprüfung durchrutscht.
Ein weiterer Punkt, warum man möglichst auf explizite Typprüfung verzichten sollte: es gibt immer Typen, die man sinnvoll auch verwenden könnte, aber durch die Robustheit des Codes nicht darf, hier also bytearrays, die äquivalent zu bytes-Objekten sind.

Da schreib ich lieber fehlerfreien Code als robusten Code.
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Wer glaubt, er hätte Datums- & Uhrzeitprogrammierung verstanden, hat Datums- & Uhrzeitprogrammierung nicht verstanden.
Falsehoods programmers believe about time
Ein Programm, das angesichts all dieser Fallstricke zu erraten versucht, was mit einem Datums-String gemeint sein könnte, ist nicht robust, sondern "robust".
Von den Problemen mit Encodings gar nicht zu reden.
In specifications, Murphy's Law supersedes Ohm's.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

narpfel hat geschrieben: Freitag 28. Mai 2021, 13:27 @LukeNukem: Das automatische Parsen sieht für mich nach einem Rezept für Datums-Mojibake aus. Woher weiß dein Programm, ob "01/02/03" entweder 2001-02-03, 2003-01-02 oder 2003-02-01 ist? Und wie ist das „korrekt und robust“?
Du hast Recht, daß das mehrdeutig ist, allerdings würde dabei 2003-01-02 herauskommen, was IMHO in fast allen Fällen auch vollkommen richtig sein dürfte. Andererseits ist die Mehrdeutigkeit halt so ein Grundproblem bei zweistelligen Jahreszahlen; vermutlich hab' ich auch deswegen schon seit Ewigkeiten keine mehr gesehen. Wie dem auch sei, dafür kennt dateutil.parser.parse() ja eigens die Parameter "dayfirst" und "yearfirst", mit denen sich dieses Verhalten beeinflussen läßt. Weg mag, kann die Konstruktoren ja so abändern, daß sie optionale "**kwargs" akzeptieren und diese dann an den Aufruf von datetime.parser.parse() durchreichen -- ich möchte aber jetzt nicht wegen fünf geänderter Zeilen nochmal den ganzen Code posten, zumal die Änderungen ja mehr als trivial sind. ;-)

Andere Alternativen wären die Module "dateparser" oder "Arrow", oder natürlich ein Eigenbau, der verschiedene Formate mit datetime.datetime.strptime() durchprobiert.

Übrigens gefällt mir auch der Vorschlag von kbr sehr gut, das in eine separate Funktion auszulagern, die dann allerdings -- notwendigenfalls parametrierbar -- im Konstruktor aufgerufen würde. Gute Idee und ein prima Tipp für unseren TO, sehr schick!
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

pillmuncher hat geschrieben: Freitag 28. Mai 2021, 14:24 Wer glaubt, er hätte Datums- & Uhrzeitprogrammierung verstanden, hat Datums- & Uhrzeitprogrammierung nicht verstanden.
Falsehoods programmers believe about time
Daraus:
Don't re-invent a date time library yourself. If you think you understand everything about time, you're probably doing it wrong.
Deswegen benutze ich für sowas gerne dateutil, das nach > 15 Jahren Entwicklung vermutlich eine gewisse Reife erreicht haben dürfte. :-)
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

@hcshm: Als Ergänzung zum Konverter: ich würde den Konverter auf keinen Fall im Konstruktor aufrufen, wobei in dieser Diskussion wohl der Initialisator gemeint sein dürfte – aber auch dort würde ich das vermeiden. Weiter würde ich ihn auch nicht bei der Argumentübergabe an die Klasse zur Instanziierung einbinden, da dies die Fehlerbehandlung undurchsichtiger machen kann. Stattdessen würde ich die Konvertierung als separaten Schritt zuvor vornehmen; gegebenenfalls mit einer nachfolgenden Validierung, sofern eine solche erforderlich ist – und nachdem der gewünschte Datentyp vorliegt.

Der Konverter darf auch vernünftige Annahmen über den Eingabedatentyp, als auch bei Strings über das Format machen. Sonst ist die Aufgabe bei mehrdeutigen Eingaben nicht möglich. Eine Konvertierung kann nicht mit allen möglichen beliebigen Eingabeformaten und -typen fehlerfrei funktionieren. Also besser einfache Konverter mit klar definiertem erlaubten Input.

Wenn all das beherzigt wird, kann dies zu kurzem, lesbaren und testbaren, sowie effizienten Code führen.
Benutzeravatar
__blackjack__
User
Beiträge: 14028
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Statt dem eigenen `ConversionError` wäre ein `TypeError` in den gezeigten Fällen sinnvoller, denn genau das wird damit dort ja angezeigt, das der Code den Typ nicht kennt und deshalb nicht will. Was letztlich „duck typing“ verhindert und damit nicht pythonisch ist.

Alles mögliche überall für den Aufrufer auf Verdacht umzuwandeln führt zu Code-Duplikationen, zu mehr Angriffsfläche für Fehler, zu mehr Tests die man schreiben muss, und in der Praxis wird sehr wahrscheinlich vieles davon am Ende überhaupt nicht verwendet.

Das der Name am Ende als `bytes` endet, statt als Zeichenkette, ist vermute ich mal ein Flüchtigkeitsfehler und sollte andersherum.

In `Ding.__init__()` fehlt der `super()`-Aufruf. Oder der Hinweis, dass man das nicht sicher mit Mehrfachvererbung verwenden kann. Und durchgereichte *args und **kwargs. Oder der Hinweis das man in abgeleiteten Klassen die Signatur nicht erweitern darf oder eben auch wieder Probleme bei Mehrfachvererbung drohen.

Beim Validieren vom Geburtstagswertebereich hat Python die praktische Operatorverkettung, so dass der Teil der in beiden Verhleichen verwendet wird, nur einmal geschrieben und ausgewertet werden muss.

In Produktiv-Code würde ich das `attr`-Modul verwenden. Das vereinfacht/beseitigt die `__init__()` und bietet hooks für Validierung. Spart also hier die Extraklasse und damit dann auch den Namenszusatz `Valid…` bei jeder Klasse wenn man das Muster konsequent durchzieht. Sollte man dieses Muster verwenden, würde ich den Namenszusatz zu der unvalidierten Klasse verschieben. `attr` verringert auch Codewiederholungen beim Validieren wenn man die Möglichkeit nutzt Validierungsfunktionen zu schreiben.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Freitag 28. Mai 2021, 16:41 Statt dem eigenen `ConversionError` wäre ein `TypeError` in den gezeigten Fällen sinnvoller, [...]
Alles richtig, stimmt... wobei ich den ConversionError schon wegen des zusätzlichen Informationsgehalts behalten wollen würde, aber vermutlich besser von TypeError geerbt hätte.

Ganz besonders freue ich mich darüber, daß es auch konstruktiv geht. Danke.
hcshm
User
Beiträge: 48
Registriert: Dienstag 11. Februar 2020, 08:23

ganz herzlichen Dank, das waren zahlreiche, enorm wichtige Gedankenanstöße!!
Antworten