Problem bei Ableitung von datetime.date Klasse

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
notGuido
User
Beiträge: 5
Registriert: Montag 15. Juli 2013, 09:50

Hallo,

sollte eigentlich einfach sein. Klasse datum von date ableiten, um deutsche Darstellung Tag.Monat.Jahr in Ausgabe und Konstruktor zu haben:

Code: Alles auswählen

from datetime import date

class datum(date):
    '''
    Datum mit deutscher Ausgabedarstellung
    '''
    
    def __init__(self,tag,monat,jahr):
        date.__init__(jahr,monat,tag)

    def __str__(self):
        "Liefert Tag.Monat.Jahr"
        return "%d.%d.%d" % (self.day,self.month,self.year)
    
    # Aliasname für Methode, der Basisklasse
    heute=date.today
    
if __name__ == '__main__':
    jetzt=datum(15,7,2013)
    
    print(jetzt)
Aber der Konstruktor springt nicht in den Basisklassenkonstruktor. Und es kommt:

Traceback (most recent call last):
File "/home/marcus/pyworkspace/Hello/datum.py", line 27, in <module>
jetzt=datum(15,7,2013)
ValueError: day is out of range for month


Any ideas?

Danke
Zuletzt geändert von Anonymous am Montag 15. Juli 2013, 09:58, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@notGuido: Meine Idee wäre es das zu lassen. Entweder grundsätzlich oder wenn dann lieber durch Komposition als durch Vererbung. Dann hast Du besser unter Kontrolle welche Methoden Deine Klasse und deren Exemplare hat. So wie Du das jetzt vorhast fehlen nämlich noch 9 Methoden bei denen Du dafür sorgen müsstest, dass Exemplare von *Deiner* Klasse geliefert werden und nicht vom original `datetime.date`.

Konkret ist hier das Problem, dass es sich bei `datetime.date`-Objekten um nicht veränderliche Exemplare handelt die nicht per `__init__()` initialisiert werden können, sondern mit `__new__()` erstellt werden. Dementsprechend musst Du statt `__init__()` eine entsprechende `__new__()`-Methode in der abgeleiteten Klasse schreiben.
notGuido
User
Beiträge: 5
Registriert: Montag 15. Juli 2013, 09:50

BlackJack hat geschrieben:@notGuido: Meine Idee wäre es das zu lassen. Entweder grundsätzlich oder wenn dann lieber durch Komposition als durch Vererbung. Dann hast Du besser unter Kontrolle welche Methoden Deine Klasse und deren Exemplare hat. So wie Du das jetzt vorhast fehlen nämlich noch 9 Methoden bei denen Du dafür sorgen müsstest, dass Exemplare von *Deiner* Klasse geliefert werden und nicht vom original `datetime.date`.

Konkret ist hier das Problem, dass es sich bei `datetime.date`-Objekten um nicht veränderliche Exemplare handelt die nicht per `__init__()` initialisiert werden können, sondern mit `__new__()` erstellt werden. Dementsprechend musst Du statt `__init__()` eine entsprechende `__new__()`-Methode in der abgeleiteten Klasse schreiben.

Danke für den Tipp mit __new__ - Doku lesen hilft ;-) "__new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. "
Was mache ich bei Kunden die z.B. noch 2.3 einsetzen, die gem. Doku kein __new__ kennen?

Nur die Argumentation mit der Composition läuft mir etwas gegen den Strich.... Ich will ja explizit 99% wiederverwenden = Sinn einer Ableitung. Die Methodennnamen könnte man umstellen siehe heute(). Aber es ging mir primär um das Ausgabeformat und die deutsche Reihenfolge im Konstruktor.

Vielen Dank nochmals.
BlackJack

@notGuido: Wenn Du ableitest musst Du halt ein drittel der API überschreiben, damit alles wie erwartet funktioniert.

Bei Kunden die noch 2.3 einsetzen freundlich anfragen warum sie etwas einsetzen was vor 10 Jahren rauskam. :-) Nachschauen wie das dort gelöst ist, insbesondere ob es vielleicht nicht doch schon ein `__new__()` gibt. Ansonsten kann es sein, dass einem dort gar nichts anderes als Komposition übrig bleibt, denn es gab ja mal einen Unterschied zwischen „(externen) Typen” und „Klassen”. Das man 99% der API wiederverwenden will spricht da übrigens auch nicht unbedingt dagegen. Zumindest für die „nicht-magischen” Methoden die man nicht überschreiben muss, kann man ja einfach eine universelle Weiterleitung per `__getattr__()` schreiben.

Grundsätzlich würde ich aber auch in Frage stellen ob man einen Datentyp „übersetzen” sollte. Das ist IMHO unnötige arbeit die wenig bis gar nichts bringt, aber eine potentielle Fehlerquelle ist. *Wenn* ich allerdings Datentypen übersetzen würde, spräche das IMHO noch mehr für Komposition, weil man dann einfache Umbenennungen auch über die `__getattr__()` und eine Zuordnung alter Name zu neuer Name erledigen kann. Oder man schaut sich Metaklassen an.

Die Reihenfolge der Argumente beim erstellen eines `date`-Objekts würde ich nicht ändern, weil das keine (natürlich)sprachliche Entwurfsentscheidung ist/war, also nicht Englisch was man in Deutsch übersetzen müsste, sondern eine Logische — die Argumente sind nach ihrem Gewicht im zusammengesetzten Wert sortiert.
notGuido
User
Beiträge: 5
Registriert: Montag 15. Juli 2013, 09:50

BlackJack hat geschrieben:@notGuido: Wenn Du ableitest musst Du halt ein drittel der API überschreiben, damit alles wie erwartet funktioniert.
...
Mhhh, mein früheres Perl-Leben würde sagen "There's always one more way to do it" ;-)

Vielen Dank für die nette fachliche Diskussion.

Gruss
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

notGuido hat geschrieben:Ich will ja explizit 99% wiederverwenden = Sinn einer Ableitung.
Nein. Oder besser gesagt, ja, schon, aber anders, als du denkst. Vererbung sollte nicht dazu dienen, in einer Kindklasse Funktionalität aus der Elternklasse zu wiederzuverwenden. Statt dessen sollte der Client-Code, der Exemplare der Elternklasse verwendet, auch mit Exemplaren der Kindklasse verwendbar sein, ohne dass dort zwischen beiden unterschieden werden muss.

Wenn ich zB. eine Verkehrssimulation programmiere, dann sollte ich nicht ein Motorrad als Fahrrad mit Motor aber ohne Pedale oder ein Fahrrad als Motorrad ohne Motor aber mit Pedalen modellieren, sondern beide von einer Schnittstellenklasse Zweirad (oder von mir aus auch Fahrzeug) ableiten, die eben keine besondere ererbbare Funktionalität, sondern nur Schnittstellenbeschreibungen in Form von zB. leeren Methoden enthält. In Python kann man das sogar komplett weglassen, weil die Schnittstelle gar nicht erst explizit hingeschrieben werden muss, da es genügt, wenn einfach beide Klassen, Fahrrad und Motorrad, dieselben Methodensignaturen besitzen. Man nennt das Duck Typing.

Der Code, der Exemplare dieser Klassen verwendet, ist das, was man wiederverwenden möchte. Man möchte nicht jedesmal, wenn ein neuer Fahrzeugtyp dazukommt, den gesamten Code durchsuchen müssen um jede Stelle anzupassen, wo ein Motorrad oder Fahrrad oder ein anderes Fahrzeug verwendet werden. Wenn man das zentrale Konzept der real existierenden Objektorientierung betrachtet, den Polymorphismus, dann ist dieser genau das Konzept, das diese Art der Wiederverwendung ermöglicht. Siehe auch hier.
Zuletzt geändert von pillmuncher am Dienstag 15. Oktober 2013, 00:31, insgesamt 1-mal geändert.
In specifications, Murphy's Law supersedes Ohm's.
notGuido
User
Beiträge: 5
Registriert: Montag 15. Juli 2013, 09:50

pillmuncher hat geschrieben:
notGuido hat geschrieben:Ich will ja explizit 99% wiederverwenden = Sinn einer Ableitung.
Nein. Oder besser gesagt, ja, schon, aber anders, als du denkst.
Erstmal Danke! für den guten Text und die hilfreichen Hinweise. Das Polymorpie-Beispiel zeigt prima in welche Hölle sich die alten if-elsif-Programmierer begeben :-)

Damit das nicht in einen OO-Workshop ausartet, der sicher interessant wird....

Die Idee hinter der Ableitung: class datum(date): war auch für Python/OO-Neueinsteiger ein griffiges Minibeispiel zu haben, um:
  • A: Das Prinzip "Du schreibst nur noch die Deltas", inbes. in Python besser gelöst, als in C++/Java, und
    B: Das Beispiel sollte so übersichtlich sein, das es "knackig" rüberkommt
    C: Selbst wenn Dir eine Klasse nicht passt, kannst Du das ableiten - anders als in C++/Java
Gruss
BlackJack

@notGuido: Warum ist das in Python besser gelöst als in C++/Java/…? Da würde Dein Beispiel doch prinzipiell genau so aussehen. Auch wenn es IMHO kein gutes Beispiel ist, weil man wie gesagt ein Drittel an Methoden hat, die sich nicht wie erwartet verhalten, wenn man sie nicht auch überschreibt, man also letztendlich einen ziemlich kaputten Typ erstellt. Du hast neben der `heute()`-Methode ja auch weiterhin eine `today()`-Methode, die in Deinem `Datum` eigentlich nichts zu suchen hat. Das ist ganz und gar kein gutes Beispiel für einen guten Programmentwurf. Diese „Übersetzung” der Klasse ins Deutsche wäre eher ein Fall für ein nettes Beispiel für Metaklassen. *Das* wiederrum ist vielleicht nicht unbendingt der geeignete Stoff für Anfänger. Und Punkt C verstehe ich auch nicht, denn auch in C++/Java/… kann ich eine Klasse ableiten wenn sie mir nicht passt.
notGuido
User
Beiträge: 5
Registriert: Montag 15. Juli 2013, 09:50

BlackJack hat geschrieben:@notGuido: Warum ist das in Python besser gelöst als in C++/Java/…?
C++/Java der Zwang Konstruktoren neu implementieren zu müssen.
C++/Java keine Ableitung von von Basic Data Types/Primitive Types (Deswegen gibt es ja die Wrapper-Klassen-Krücke)
C++ kein direkter Aufruf von überladenen Konstruktoren dergleichen Klasse (z.B. Default-Konstruktor greift auf Vollständigen Konstruktor zurück)
:
:

da hat Python den einheitlicheren Ansatz ohne Ausnahmeregeln
BlackJack

@notGuido: Man kann in Python zwar von den Grunddatentypen ableiten, das wird aber sehr selten gemacht. Denn man muss dabei viele Methoden überschreiben, damit damit das funktioniert und meistens braucht man das was man da schreiben muss gar nicht wirklich. Da hat man in der Regel mit Komposition weniger Arbeit.
Antworten