zwei Datumsangaben vergleichen

Django, Flask, Bottle, WSGI, CGI…
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

... und das bekomme ich auch nicht hin:
Ich möchte Einträge mit Anmeldung vor einem bestimmtne Datum suchen:

Code: Alles auswählen

auswahl = User.objects.filter('date_joined'<datetime.date(2023,1,1))

Code: Alles auswählen

descriptor 'date' for 'datetime.datetime' objects doesn't apply to a 'int' object
... handelt es sich bei "date_joined" um eine Ganzzahl?
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

importierst du datetime oder datetime.datetime in dem Modul? Zeig' mal bitte dein Importstatement.

Vermutung: du verwendest `datetime.datetime.date()`, was eine Methode ist, die keine Argumente nimmt. `datetime.datetime.date()` liefert ein date-Objekt zu einem datetime-Objekt. Wenn du ein date-Objekt generieren willst brauchst du nur `datetime.date(2023, 9, 14)`

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ihr kennt meinen Code wahrscheinlich besser als ich und das mit den Methoden habe ich wohl immer noch nicht kapiert. Also ich importiere

Code: Alles auswählen

from datetime import date, datetime, timedelta, time
und habe jetzt alle Möglichen Kombinationen ausprobiert (besser als try and error kann ich es wieder nicht), ich bekomme es nicht hin.

Code: Alles auswählen

auswahl = User.objects.filter(datetime.date('date_joined')<datetime.date('2023,1,1'))

Code: Alles auswählen

descriptor 'date' for 'datetime.datetime' objects doesn't apply to a 'str' object
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok - das sind zwei Fehler drin. `datetime.date()` liefert ein date-Objekt zu datetime. Beispiel

Code: Alles auswählen

>>> from datetime import datetime, date
>>> foo = datetime.now()
>>> foo.date()
datetime.date(2023, 9, 14)
>>> bar = datetime.date(2023, 9, 14)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor 'date' for 'datetime.datetime' objects doesn't apply to a 'int' object
>>>
Wenn dein Django-Modell richtig angelegt ist, dann ist `date_joined` ein datetime-Objekt, da brauchst du nichts umwandeln. Außerdem funktioniert der kleiner oder größer Vergleich in Django Queries anders, siehe https://docs.djangoproject.com/en/4.2/r ... sets/#date und https://docs.djangoproject.com/en/4.2/r ... rysets/#gt

Ungetestet:

Code: Alles auswählen

auswahl = User.objects.filter(date_joined__lt=date('2023,1,1'))
Gruß, noisefloor
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Statt irgendwelchen Code zu raten und dann zu verzweifeln, sollte man systematisch an neue Probleme herangehen.
Wenn Du nicht weißt, wie man mit datetime arbeitet, dann lese zuerst die Dokumentation und mache dann einfache Codebeispiele, bis Du VERSTANDEN hast, wie man damit arbeitet, und erst dann kannst Du Dein Wissen auf ein komplizierteres Problem, wie das Abfragen von Datenbanken anwenden.
`datetime.date` ist offensichtliche eine Methode der Klasse `date`.
Um ein Date-Objekt zu erzeugen, kann man offensichtlich auch keinen irgendwie geratenen String übergeben.
All das kann man ganz einfach im interaktiven Python ausprobieren:

Code: Alles auswählen

In [1]: from datetime import datetime, date

In [2]: datetime.date
Out[2]: <method 'date' of 'datetime.datetime' objects>

In [3]: date
Out[3]: datetime.date

In [4]: date("2023,1,1")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-257-59cb6c22436d> in <module>
----> 1 date("2023,1,1")

TypeError: an integer is required (got type str)

In [5]: date(2023,1,1)
Out[5]: datetime.date(2023, 1, 1)
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

@sirius: du hast leicht reden - komme mal in mein Alter! .)
Glaub mir, ich habe die Dokumentation gelesen und zumindest versucht das alles zu verstehen. Aber ich habe schon mehrmals darauf hingewiesen, dass ich alzu komplexe Zusammenhänge einfach nicht begreife. Ich versuche, das mal zu erklären: Ich habe mich viele Jahre lang mit der Technik und den Hintergründen der Computer auseinander gesetzt und kann mir unter Bits und Bytes wirklich was vorstellen und auch wie diese gespeichert werden. Auf der Unterschied zwischen Integer, String und Fließkomma und deren Speicherung kann ich mir was vorstellen. In der Basic Welt konnte ich dies auch alles nachvollziehen (auch den physischen Hintergrund, wie diese Daten gespeichert werden). In meiner Basic Welt wurde ein Datum als Kommazahl gespeichert. Der Ganzzahlwert, war der Tag seit (geanues Datum weiß ich nicht mehr) und die Kommastelle dahinter die Teile des Tages. Ebenso konnte ich mal DBase programmieren und mir auch was unter Datenbank und Indizierung vorstellen.
In diesem Beispiel hier war für mich der Umgang dem Datum nicht mehr nachvollziehbar - es ist für mich eine Blackbox. "datetime.date("2023, 1, 1")" will der Code nicht, da es ein String darstellt und "datetime.date(2023, 1, 1)" (was ich in meinem ursprünglichen Post ja so im Code hatte - weil ich die Dokumentation gelesen habe) wird akkzepiert, da es ein integer ist? - warum ist "2023,1,1" und "date_joined" ein integer?
Mein Fehler war ja "<" anstelle von "--lt" und "datetime.date" anstelle von "date" und hier kannst du wieder mein Alter erkennen: Beide Fehler habe ich schon mal gemacht!
Ach ja und im interaktiven Python kann ich auch "date_joined" nicht ausprobieren und da hat mich schon in meinem Code die Fehlermeldung irritiert, dass es sich dabei um ein integer-Wert handelt.
Ich tue mein Bestes!
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Im interaktiven Python der ursprüngliche Fehler:

Code: Alles auswählen

In [2]: from datetime import datetime

In [3]: datetime.date(2023, 1, 1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 datetime.date(2023, 1, 1)

TypeError: descriptor 'date' for 'datetime.datetime' objects doesn't apply to a 'int' object
`datetime` ist hier nicht das Modul `datetime` sondern die Klasse `datetime` im Modul `datetime`. Und Du versuchst hier die Methode `date` von `datetime.datetime` mit drei Integer-Werten *auf der Klasse* aufzurufen und da beschwert sich Python weil bei Methoden das erste Argument von dem Typ sein muss auf dem die Methode aufgerufen wird. Oder einem davon abgeleiteten Typ. Denn das ist ja das `self`-Argument. Und 2023 ist eben eine ganze Zahl und kein `datetime.datetime`-Objekt.

Ist an der Stelle halt ein bisschen blöd das es sowohl `datetime.date` als Typ gibt, als auch `datetime.datetime.date()` als Methode auf einer Klasse. Mit ein Grund warum ich persönlich `datetime.date` und `datetime.datetime` beim Importierten immer mit ``as`` umbenenne, so das die beiden Typen der Namenskonvention für Klassen entsprechen. Damit man da nicht durcheinander kommt und weiss, das `datetime` das Modul ist, und `DateTime` die Klasse.

Code: Alles auswählen

In [4]: from datetime import date as Date

In [5]: Date(2023, 1, 1)
Out[5]: datetime.date(2023, 1, 1)
Der nächste Schritt ist dann der Vergleich mit ``<`` und einer Zeichenkette, wo man beim lesen des Codes eigentlich schon weiss, dass das nicht geht, weil eine Zeichenkette einen anderen Typ darstellt als ein Datum. Selbst wenn das Datum tatsächlich eine Zahl wäre, ginge das ja auch nicht.

Code: Alles auswählen

In [6]: "date_joined" < Date(2023, 1, 1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 "date_joined" < Date(2023, 1, 1)

TypeError: '<' not supported between instances of 'str' and 'datetime.date'

In [7]: "date_joined" < 738521
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 "date_joined" < 738521

TypeError: '<' not supported between instances of 'str' and 'int'
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pitwheazle: Du machst es Dir schwieriger, als es sein müßte. Man schreibt nicht erst einen furchtbar komplizierten Ausdruck, den man selbst nicht versteht und ärgert sich dann, dass das der Computer auch nicht versteht, sondern entwickelt ein Programm Stück für Stück, indem man kleine Teile schreibt, die man versteht, und erst, wenn man die Teile verstanden hat, sie zu komplizierteren Ausdrücken zusammenbaut.

Und auch Django kann man in der Interaktiven Shell benutzen.

Code: Alles auswählen

In [1]: from django.contrib.auth.models import User

In [2]: from datetime import date as Date

In [3]: User.objects.filter(date_joined__lt=Date(2013,1,1))
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Vielen Dank für eure Hinweise - zwischenzeitlich klappt es.
Sirius3 hat geschrieben: Freitag 15. September 2023, 14:51 @Pitwheazle: Du machst es Dir schwieriger, als es sein müßte. Man schreibt nicht erst einen furchtbar komplizierten Ausdruck, den man selbst nicht versteht und ärgert sich dann, dass das der Computer auch nicht versteht, sondern entwickelt ein Programm Stück für Stück, indem man kleine Teile schreibt, die man versteht, und erst, wenn man die Teile verstanden hat, sie zu komplizierteren Ausdrücken zusammenbaut.
Nun ja, für kleine Programmteile ist es jetzt schon etwas spät. Ich habe meine "rechentrainer.app" vor etwa drei Wochen freigeschaltet, es haben sich etwa 450 Nutzer angemeldet und fast 40 000 Aufgaben gerechnet und der/das längste view.py hat etwa 5200 Zeilen.
Jetzt muss ich mein Programm verwalten und Änderungen einbauen.
Sirius3 hat geschrieben: Donnerstag 14. September 2023, 19:22 Statt irgendwelchen Code zu raten und dann zu verzweifeln, sollte man systematisch an neue Probleme herangehen.
Wenn Du nicht weißt, wie man mit datetime arbeitet, dann lese zuerst die Dokumentation und mache dann einfache Codebeispiele, bis Du VERSTANDEN hast, wie man damit arbeitet, und erst dann kannst Du Dein Wissen auf ein komplizierteres Problem, wie das Abfragen von Datenbanken anwenden.
Das mit dem Verstehen ist mir noch nicht so ganz gelungen. Die eine Sache ist das Verstehen, wie man den Code anwendet, das andere, wie er funktioniert. Beim ersten vergesse ich immer mal wieder was oder verliere den Überblick, das zweite ist mir bei diesem Datumproblem noch nicht gelungen. Nach meinem Wissen muss die CPU ja im Endeeffekt den Inhalt zweier Speicherinhalte vergleichen und da stehn ja keine Objekte drin sondern Strings, Ganzzahlen oder auch Gleitkommawerte. Ich nehme an, dass das bei Python nicht viel anders ist als bei "meinem" StarBasic und das Datum als Gleitkommazahl gespeichert ist. Bei StarBasic ist das heute um 12 Uhr "42.326,5" (die Tage seit dem 30. Dezember 1899). Und ich gehe davon aus, dass auch Pythoncode einen solchen Wert irgendwie erzeugen muss - kann man diesen irgendwie anzeigen lassen? Ich traue mir zu, einen Code zu schreiben, der aus einem Datum einen derartigen Wert errechnet (man muss halt bedenken dass nicht jedes vierte Jahr ein Schaltjahr ist). Ich denke, das könnte ich auch unabhängig davon, ob das Datum als String oder als Integer Werte übergeben wird.
__blackjack__ hat geschrieben: Freitag 15. September 2023, 13:23 Der nächste Schritt ist dann der Vergleich mit ``<`` und einer Zeichenkette, wo man beim lesen des Codes eigentlich schon weiss, dass das nicht geht, weil eine Zeichenkette einen anderen Typ darstellt als ein Datum. Selbst wenn das Datum tatsächlich eine Zahl wäre, ginge das ja auch nicht.
Ich vermute, dass hier im Endeffekt weder Strings noch Datumsangaben verglichen werden, sondern Kommazahlen und man hier halt wissen muss, wie der Code geschrieben werden muss damit diese erzeugt werden.
Mein kleiner Programmteil:

Code: Alles auswählen

def update(req):
    if not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    auswahl = User.objects.filter(date_joined__lt=date(2023,8,1))
     for a in auswahl:
        print(a)   
    return HttpResponse("fertig!")
... macht was er soll.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@Pitwheazle: Es ist nie zu spät für kleine Programmteile. Das wäre ja auch fatal, weil sonst jedes größere Projekt unwartbar wäre.
Genau den nicht funktionierenden Programmteil hätte man so Stück für Stück neu geschrieben. Eben so weit, wie er funktioniert - und man ihn versteht.

Mit dem Rest deines Posts bist du in meinen Augen auf Irrwegen. Wie da intern etwas gespeichert wird ist irrelevant und vor allem abhängig von der Datenbank. Python hat damit erst mal nichts zu tun. Der ORM Mapper gibt aber den Syntax in Python vor. Und da gibt es kein < und >. Und diese Operatoren würden, würde es sie geben, auch bei Zeichenketten keinen Sinn machen. Das ist, was __blackjack__ gesagt hat. Deine Antwort passt aber nicht zu dem.

Fakt ist: Zeitatempel sind keine einfache Zahl. Macht auch wenig Sinn, wenn man sich in Erinnerung ruft, dass da auch eine Zeitzone enthalten sein kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Doch da werden Objekte verglichen. Und zwar nicht von der CPU sondern von Code der mit mindestens einem der beiden Datentypen verbunden ist, die da verglichen werden. Und wie Zahlen, Zeichenketten, und Datumsangaben *intern* gespeichert sind weiss man in der Regel *nicht*. Das geht uns nichts an. Vielleicht wird das Datum intern als *eine* Zahl gespeichert, vielleicht auch nicht. Wenn dann eher nicht als Gleitkommazahl die die CPU versteht, denn die sind ja nicht besonders präzise was den Nachkommaanteil angeht. Und der Bezugspunkt kann ja auch relativ beliebig sein, wenn es eine Zahl ist. Man könnte im Quelltext nachschauen — aber dann wüsste man auch nur wie das in CPython und in der Version gelöst ist die man sich da gerade anschaut. Andere Versionen oder andere Implementierungen können das komplett anders lösen. Für uns als Programmierer ist nur die API wichtig die das Objekt nach aussen anbietet.

Der ``<``-Vergleich zwischen Zahlen oder Zeichenketten und `datetime.date`-Objekten schlägt fehl weil keines der beteiligten Objekte weiss, wie es sich mit dem jeweils anderen vergleichen soll. Als erstes wird nämlich der Datentyp geprüft, ob damit ein Vergleich sinnvoll möglich ist. Und das ist es halt nicht, und weiter geht das dann auch gar nicht. Zu dem Wert des Objektes kommt der Vergleich nie, das scheitert schon am Typ.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

und wenn du wissen willst, wie der Wert in der Datenbank gespeichert ist, dann schau' doch einfach in der Datenbank nach. Direkt, ohne über das Django ORM zu gehen.

Wie aber schon gesagt wurde: am Ende ist das total Latte, weil letztendlich nur relevant ist, was für ein Objekt dir das Django ORM liefert. Wie oft das eventuell wo von wem konvertiert wurde ist für uns als Nutzer von Django irrelevant.
Die SQLite Datenbank kennt z.B. gar keinen eigenen Datentyp für ein Datum (siehe Link). Das Django ORM (und auch andere ORM wie SQLAlchemy) können trotzdem damit umgehen und liefern dir auch wieder bei einer Abfrage ein Date- oder Datetime-Objekt zurück. Wie das low-level implementiert ist brauchen du, ich und die meisten anderen nicht verstehen.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

noisefloor hat geschrieben: Samstag 16. September 2023, 21:03 und wenn du wissen willst, wie der Wert in der Datenbank gespeichert ist, dann schau' doch einfach in der Datenbank nach. Direkt, ohne über das Django ORM zu gehen..
Wie das low-level implementiert ist brauchen du, ich und die meisten anderen nicht verstehen.
In der Datenbank steht das ja auch nicht so wie es digital gespeichert wird und ich bin halt neugierig und ich wüsste trotzdem gerne wie das "low-level implementiert" ist und vermute weiterhin, dass im Endeffekt die CPU zwei Speicherinhalte mit Fließkommazahlen vergleicht und die Genauigkeit von 10 hoch -7 einesTages (bzw. 10 hoch -15) für eine Datumsangabe wahrscheinlich genau genug ist.
Nachtrag: Ich bin schon aus der Kirche ausgetreten da man dort auch der Meinung ist, dass man nicht alles verstehen muss ... und dachte auch, dass wir dies seit dem Zeitalter der Aufklärung hinter uns haben :).
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst wirklich alles verstehen? Also so Dinge wie der Kohlenstoffanteil im Stahl deiner Bremsscheiben im Auto kontrolliert wird? Oder die Bedarfsplanung für die örtliche Müllabfuhr? All diese Dinge weißt du? Oder reicht es in dieser sehr komplexen Welt vielleicht doch oft, sich auf das Ergebnis zu konzentrieren? Das Auto bremst, und der Müll wird abgeholt. Hoffentlich. Und das ist hier genauso. Zum erfolgreichen arbeiten muss man sich damit nicht auskennen, solange das System tut, was es soll.

Wenn der Schuh aber so sehr drückt, weil der Freigeist raus will - use the source, Luke. Ist alles quelloffen. Betriebssystem je nach Vorliebe auch, so ein Datumswert will ja auch irgendwie auf dem Massenspeicher landen.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
In der Datenbank steht das ja auch nicht so wie es digital gespeichert wird und ich bin halt neugierig und ich wüsste trotzdem gerne wie das "low-level implementiert" ist
Also ganz low-level sind es Nullen und Einsen... Wenn ich nicht irre speichert das Django-ORM ein Datum ein einem Textfeld in SQLite. Also als String, nicht als Fließkommazahl. Um die Konvertierung von Text -> Date-Objekt kümmert sich das ORM. Wenn dich das interessiert ->Quellcode lesen.

Letztendlich ist das auch egal, weil es dein Ausgangsproblem nicht behoben hätte. Weder deine inzwischen gelüftet Verwirrung bzgl .datetime.datetime.date noch die falsche Syntax für Vergleichsoperationen bei `filter`. I.d.R. programmiert man auch nicht besser, wenn man im Detail die low-level Implementierung kennt. Als Programmierer muss man primär die API kennen bzw. gezielt effektiv nach allem Suchen können, was man nicht auswendig weiß. Besonders Django ist ja seeeehr ausführlich dokumentiert plus es gibt seeeehr viele Beispiele im Netz (Stack Overflow usw.), was man relativ schnell die Lösung zum seinem Problem hat. Ich brauche glaube ich noch nie weiter als der 4 oder 5 Suchtreffer bei Google lesen, um meine Fragen zu Django zu lösen.

Gruß, noisefloor
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Dass man Implementierungsdetails verstehen möchte, ist IMHO nicht schlecht. Aber (und das ist oft ein Problem) muss man es auch (und sogar noch viel mehr) können, Sachen zu benutzen, ohne genau zu wissen, wie es implementiert ist.

Python ist, wie quasi jede andere Sprache auch, eine mathematische Beschreibung von einem Programm, nicht eine physikalische. Es wird beschrieben, was gemacht werden soll, nicht wie.

Nur um das mal am Beispiel `datetime.datetime` zu verdeutlichen: Die Dokumentation beschreibt, was man mit `datetime`-Objekten machen kann. Z. B. kann man neue Objekte erstellen (`datetime.now()` etc.), man kann ihr Jahr, Monat, Tag, etc. abfragen (aber nicht ändern), man kann sie vergleichen und mit `timedelta`-Objekten verrechnen, etc.

An keiner Stelle wird geschrieben, wie ein `datetime`-Objekt implementiert ist. Weil das in erster Linie nicht wichtig ist, weil man ja sinnvolle Sachen mit `datetime`-Objekten machen will und nicht Elektronen dabei zugucken will, wie sie sich in einer CPU bewegen.

Wenn man jetzt wissen will, wie ein `datetime`-Objekt „intern“ funktioniert, muss man sich erstmal eine Implementierung von Python aussuchen, bei der man sich das angucken will. Weil ja gerade nicht festgelegt ist, wie ein solches Objekt aufgebaut ist. Und natürlich noch eine spezielle Version der Implementierung, weil sich das ja alles ändern kann.

In der aktuellen Version von CPython sieht das dann so aus:

Code: Alles auswählen

/* # of bytes for year, month, day, hour, minute, second, and usecond. */
#define _PyDateTime_DATETIME_DATASIZE 10

/* The datetime and time types have hashcodes, and an optional tzinfo member,
 * present if and only if hastzinfo is true.
 */
#define _PyTZINFO_HEAD          \
    PyObject_HEAD               \
    Py_hash_t hashcode;         \
    char hastzinfo;             /* boolean flag */

#define _PyDateTime_DATETIMEHEAD        \
    _PyTZINFO_HEAD                      \
    unsigned char data[_PyDateTime_DATETIME_DATASIZE];

typedef struct
{
    _PyDateTime_DATETIMEHEAD
    unsigned char fold;
    PyObject *tzinfo;
} PyDateTime_DateTime;          /* hastzinfo true */
Wenn man die Makros halbwegs aufdröselt, hat man also ungefähr das hier:

Code: Alles auswählen

typedef struct
{
    PyObject_HEAD
    Py_hash_t hashcode;
    char hastzinfo;             /* boolean flag */
    unsigned char data[10];
    unsigned char fold;
    PyObject *tzinfo;
} PyDateTime_DateTime;          /* hastzinfo true */
Wie man sieht, ist ein `datetime`-Objekt ein Python-Objekt (wegen `PyObject_HEAD`), hat einen Hashwert, ein Flag, das sagt, ob es Zeitzoneninformationen hat, 10 Byte an „Daten“, ein Byte, das sagt, ob das erste oder zweite Mal gemeint ist, an dem das Datum war (Stichwort Zeitumstellung) und dann noch ein Zeitzonen-Objekt.

Das Problem an der Sache ist: Das sagt immer noch nicht, wie so ein Objekt „intern“ funktioniert. Weil das wieder eine Beschreibung in einer Programmiersprache (in diesem Fall C) ist, die wieder nur das was und nicht das wie beschreibt (der C-Standard beschreibt das Verhalten von C in Bezug auf eine „abstrakte Maschine“, also ein mathematisches Objekt, und nicht in Bezug auf reale Hardware, die tatsächlich existieren würde). Man müsste sich jetzt also wieder eine spezielle Implementierung von C suchen und sich da angucken, wie `struct`s, Zeiger, Arrays etc. implementiert sind. (Und das geht natürlich nicht allgemein, sondern nur in speziell einem Fall, weil die C-Implementierung jedes einzelne `struct` oder Array (etc.) anders als alle anderen implementieren könnte. Weil das wie eben nicht genau festgelegt ist, sondern nur, was das Programm am Ende tun soll, wenn es ausgeführt wird.)

Und das sagt immer noch nicht, wie das alles eigentlich™ funktioniert, weil die C-Implementierung ja auch irgendwie implementiert sein muss. Also z. B. als Interpreter (dann sind wir wieder bei Schritt 1, wie ist der Interpreter aufgebaut) oder als Compiler, der irgendeinen Maschinencode ausspuckt, der dann auf einer realen CPU ausgeführt wird.

Aber halt, wie läuft das Programm dann auf der CPU? Jedenfalls nicht so, wie der Maschinencode das Programm beschreibt. So war eine CPU vielleicht mal vor 40 Jahren aufgebaut, aber heutzutage wäre das viel zu langsam. Also übersetzt die CPU intern den Maschinencode in eine Form, die schneller ausgeführt werden kann als der eigentliche Maschinencode. Und führt die einzelnen Instruktionen in einer anderen Reihenfolge aus als sie eigentlich im Programm vorkommen. Und Speicherzugriffe? Viel zu langsam, da macht die CPU auch was anderes als eigentlich™ im Programm steht.

Worauf ich hinaus will: Das ist alles ein riesiges Fraktal an Komplexität, das man alles gar nicht verstehen kann. Weil es einfach alles viel zu viel ist. Abstraktion ist das einzige, was dagegen hilft. Und in diesem Fall ist die Abstraktion halt, dass ein `datetime`-Objekt keine von der Sprache Python festlegbare interne Struktur haben kann, sondern dass man das Objekt so verwendet, wie es in der Doku beschrieben ist. Also z. B. dass `a < b` einen Vergleich macht, ob `a` früher als `b` ist, aber nicht wie. Weil das „wie“ zu beschreiben noch viel komplizierter wäre als das, was ich gerade nur über die Struktur von `datetime`-Objekten geschrieben habe.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

__deets__ hat geschrieben: Sonntag 17. September 2023, 14:35 Du musst wirklich alles verstehen? Also so Dinge wie der Kohlenstoffanteil im Stahl deiner Bremsscheiben im Auto kontrolliert wird? Oder die Bedarfsplanung für die örtliche Müllabfuhr? All diese Dinge weißt du?
... nein, weder weiß ich nicht noch will ich alles wissen. Aber um den Kohlenstoffanteil im Stahl und die Vorgänge im Hochofen habe ich mich schon mal gekümmert ... und wie eine Restaurantküche organisiert ist interessiert mich sehr ... Ich finde nur einen Satz wie "Wie das ....egal was .... ist brauchen du, ich und die meisten anderen nicht verstehen" komisch.

Meine Güte, da habt ihr euch aber ziemlich reingekniet. Vielen Dank dafür - OK, ich gebe auf ... aber das hier finde ich am erhellensten (Ich dachte schon, dass die unterste Stufe immer noch zumindest so ähnlich wie Maschinencode ist):
narpfel hat geschrieben: Sonntag 17. September 2023, 16:31 Aber halt, wie läuft das Programm dann auf der CPU? Jedenfalls nicht so, wie der Maschinencode das Programm beschreibt. So war eine CPU vielleicht mal vor 40 Jahren aufgebaut, aber heutzutage wäre das viel zu langsam. Also übersetzt die CPU intern den Maschinencode in eine Form, die schneller ausgeführt werden kann als der eigentliche Maschinencode. .... Und Speicherzugriffe? Viel zu langsam, da macht die CPU auch was anderes als eigentlich™ im Programm steht.
... aber wie kann das ganze ohne Speicherzugriffe funktionieren?
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Pitwheazle: In erster Linie Caches. Der RAM ist soo langsam, dass die CPU einen Großteil der Zeit damit verbringen würde, auf den Speicher zu warten. Also merkt sie sich, welche Speicherbereiche kürzlich benutzt worden sind und speichert sie in einem schnelleren Speicher zwischen. Und wenn der Cache voll ist, wird er mit dem RAM synchronisiert. Und weil das immer noch zu langsam ist, haben moderne CPUs inzwischen drei verschiedene Caches, wo die nächste Stufe jeweils größer und gleichzeitig langsamer wird.

Und dann sind da noch so Fälle wie dieser hier: Die CPU erkennt, wenn man Nullen speichert, und optimiert das, sodass die Nullen nicht wirklich in den Speicher kopiert werden müssen. Bis das dann irgendwann nicht mehr funktioniert, weil die CPU selbst programmierbar ist und durch ein Softwareupdate verändert werden kann (damit ist explizit nicht der Maschinencode gemeint, der von der CPU ausgeführt wird, sondern der Code eine Stufe unter dem Maschinencode, der das Verhalten der CPU selbst beschreibt. Korollar daraus: Die „Hardware“ ist keine Hardware.)

Ach, und falls du glaubst, CPUs führen Code immer richtig aus, oder zumindest, dass alle CPUs gleichen Modells Code gleich ausführen und dass defekte CPUs bei der Herstellung rausgefiltert werden: Nein! Und das ist komplett unabhängig von der Möglichkeit, dass der Zustand der CPU oder des RAMs zufällig z. B. durch kosmische Strahlung verändert wird.

Wie gesagt: Fraktale Komplexität.

Und was hat das jetzt alles mit `datetime.datetime` zu tun?
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Mit Gleitkommazahlen gibt es Präzisionsprobleme. Nehmen wir mal an der ganzzahlige Anteil sind die Tage, dann kann man beispielsweise Stunden schon nicht präzise im Nachkommaanteil repräsentieren:

Code: Alles auswählen

In [143]: h = 1 / 24

In [144]: h
Out[144]: 0.041666666666666664

In [145]: d = 0

In [146]: for _ in range(24):
     ...:     d += h
     ...: 

In [147]: d
Out[147]: 0.9999999999999996
Das wäre also keine gute Idee das so zu repräsentieren und damit zu rechnen, wenn man auch Ganzzahlen verwenden könnte. Das als Gleitkommazahl zu behandeln hat seinen Ursprung in BASIC-Dialekten sehr wahrscheinlich darin, dass die Rechenroutinen für Gleitkommazahlen schon da waren und man keinen neuen Datentyp implementieren konnte oder wollte, und vor allem innerhalb von Tabellenkalkulationen letztlich sowieso alles was nicht bei drei auf einem Baum ist, durch eine Gleitkommazahl repräsentiert wird, wenn es kein Text ist.

Microsoft hat damit soweit ich weiss bei QuickBASIC angefangen mit „serial number“ für Zeitpunkte als IEEE Gleitkommazahlen mit doppelter Präzision.

Der Vergleich mit der Kirche hinkt ein bisschen. Die wollen nicht, dass man bestimmte Sachen genauer weiss, weil sie wollen, dass das alles so bleibt und sich für *sie* nichts ändert. Die Begründung bei Abstraktionsschichten in der IT ist quasi das Gegenteil: Man muss nicht wissen wie das hinter der API funktioniert, damit das alles anders werden kann, ohne das sich für Dich etwas ändert. Ob `datetime` intern mit einer Gleitkommazahl, oder einer Ganzzahl, oder mehreren Ganzzahlen, oder was auch immer arbeitet, und was der 0-Punkt/Bezugspunkt ist, braucht einen nicht zu interessieren solange sich das nach aussen so verhält wie es dokumentiert ist. Python Version X kann dann etwas ganz anderes machen als Python Version Y — für Dein Programm ändert sich nichts. Das gilt auch verschiedene Implementierungen. Es gibt ja auch IronPython und Jython. Vielleicht benutzen die was auch immer .NET oder die JVM Standardbibliotheken für Zeitpunkte/Datumsrechnungen bieten, als Grundlage. Das kann dem Programmierer egal sein, solange die am Ende die gleiche API im `datetime`-Modul anbieten.

Das in modernen CPUs nicht mehr direkt Maschinensprache ausgeführt wird, und nicht in der angegebenen Reihenfolge, oder das ein Speicherzugriff auf eine Adresse nicht mehr einen direkt über diese Adresse addressierten RAM-Baustein meint, sondern auch aus dem Cache oder von der Festplatte kommen kann, muss man nicht zwingend wissen, weil es im Endeffekt keinen semantischen Unterschied macht. Das Ergebnis ist das gleiche als wenn der Maschinencode direkt in der angegebenen Reihenfolge auf direkt adressiertem RAM gelaufen wäre.

Meine ersten Rechner haben zwar Maschinencode direkt auf der CPU ausgeführt und das kam aus RAM und operierte auf RAM der quasi direkt über die Adressleitungen aus der CPU mit den Adressleitungen der Speicherbausteine verbunden sind, aber auch damals gab es schon einen Haufen Abstraktionen, wo man die Details nicht zwingend wissen musste. Selbst auf Maschinensprache-Ebene. Das typische „Hallo Welt“ in Maschinensprache auf dem C64 kann beispielsweise so aussehen:

Code: Alles auswählen

        ldx #0
loop    lda hello,x
        beq done
        jsr $ffd2   ; CHROUT
        inx
        bne loop
done    rts

hello   .text "hello, world!"
        .byte 13, 0
Das ist der gleiche Code, der auch auf früheren (PET, VIC-20) und späteren (C16/C116/Plus 4, C128) Commodore-Rechnern mit dem gleichen Prozessor läuft, obwohl die unterschiedliche Video-Chips und Bildschirmauflösungen haben. Weil Commodore die Ausgabe von einem Zeichen als Unterprogramm im ROM an Adresse 0xFFD2 zur Verfügung stellt.

Wobei das nicht einmal der Bildschirm sein muss, denn man kann das Ausgabegerät, ebenfalls mittels ROM-Routinen an festen Adressen, vorher ändern. Dann wird der Text in eine Datei geschrieben (Band oder Diskette) oder über den Parallelport oder die serielle Schnittstelle an einen Drucker, Plotter, … oder anderen Rechner übertragen. Wie das genau passiert, und das kann von Rechner zu Rechner unterschiedlich sein, weil die unterschiedliche Bus-Systeme und Peripherie-Chips verwenden, muss mich nicht interessieren. Welcher I/O-Chip welche Leitungen ansteuert, wie die Daten auf Band aufgezeichnet werden, ob Laufwerke per IEEE-488 oder IEC-Bus angeschlossen sind, was für ein Dateisystem auf der Diskette verwendet wird, ob der Drucker parallel per Centronics-Schnittstelle oder IEEE-488-Bus oder seriell per RS232 oder IEC-Bus angeschlossen ist, oder wie das Modem das Handshaking mit dem Rechner abwickelt, sind alles Sachen die interessant sind, und denen man auch nachrecherchieren kann, aber für das Programm zur Ausgabe von einem Text muss man das alles nicht wissen.

Und wenn man nicht bis auf den Prozessor runter geht beim Programmieren, dann ist die Programmiersprache schon eine Abstraktionsschicht. Bei einem PRINT"HELLO, WORLD!" muss mich nicht einmal interessieren, dass die Zeichen auf all den genannten Rechnern über ein Maschinensprache-Unterprogramm ausgegeben werden, das an der immer gleichen Adresse anspringbar ist. Nicht einmal ob das überhaupt der gleiche Prozessor ist, denn das funktioniert gleichwertig auf 6809, 8088, ARM, RCA 1802, RISC-V, x86, Z80, … -Systemen, egal welche Hardware da angeschlossen ist, und wie die angeschlossen ist (solange es etwas gibt wo man Text ausgeben kann).

Selbst auf Maschinencode-Ebene ist das alles andere als neu. Beispiel: Die CHIP-8 VM, die um einiges älter ist als die ganzen Commodore-Rechner. In den 1970ern haben die Leute schon Maschinenprogramme geschrieben die gar nicht direkt für eine reale CPU gedacht waren, um die Programmierung zu vereinfachen und portabler zu machen. Wo den Programmierer dann auch nicht interessieren musste wie das der Prozessor tatsächlich macht und wie die Pixel letztendlich auf den Bildschirm gelangen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Hallo Leute,
(dauert immer einen Moment bis ich antworte - ich bin in Portugal "on the road").
Das ist ganz toll, wie ihr euch da Mühe gebt, mir den Durchblick zu verschaffen. Ich finde das alles sehr interessant und erhellend.
Ich habe jetzt mal nachgeschaut. Bei StarBasic (der Makrosprache von OpenOffice) wird das Datum als "double" abgespeichert - mit 8 Byte und als "Dezimalzahlen im Bereich von + 1,79769313486232 * 10E308 bis
4,94065645841274 * 10E–324". Eine Sekunde entspricht dann also 0,000 011 57 (ich weiß nicht, ob das Datum in StarBasic dort genauer als auf die Sekunde vorgesehn ist - es ließen sich aber doch sogar Millisekunden so darstellen). Dadurch könnte man aber doch auch auf 8 Stellen runden (bei Gleitkomma mit doppelter Genauigkeit).
Das mit den Caches bei den Prozessoren ergibt für mich dann jetzt auch Sinn. Ich nehme an, die schnellen sind dann einfach näher am "Rechenkern". Aber auch der Cache ist doch ein Speicher. Und arbeitet mit Adressen und Speicherinhalt?
Das mit der Verlässlichkeit von CPUs habe ich schon gelesen. Wenn ich mich nicht irre, werden speziell Pentium1 (?) in Satelliten eingebaut, da modernere Prozessoren aufgrund der zu kleinen Abstände der Leiterbahnen von kosmischer Strahlung nicht zuverlässig arbeiten.
Das mit dem Kirchenvergleich war nicht wirklich ernst gemeint :)
Danke wiedermal für euren unermüdlichen Einsatz für meine Neugier!
Antworten