in x.__dict__ sind nur die Elemente von Student.
x.__class__.mro() zeigt dann, wie weiter gesucht wird, wenn die Variable
nicht in _dict_ ist.
Aber die eigentliche Frage ist: Warum willst du das herausfinden?
In normalen Programmen muss man das nicht wissen.
>Aber die eigentliche Frage ist: Warum willst du das herausfinden?>In normalen Programmen muss man das nicht wissen.
Ich würde gerne die Klassen einzeln serialzieren, deshalb wollte ich
prüfen, woher die Variable kommt.
Dirk schrieb:> serialzieren
Ok. Das ist eines der wenigen Einsatzzwecke von mro().
Allerdings gibt es bereits einen eingebauten (de-)serialisierer. Er
heißt pickle.
Dirk schrieb:> Pickle hatte ich mir schon angeschaut, aber wenn ich es richtig verstehe> geht nur JSON und ich nicht C-Datentypen.
Es geht alles, was Python ist.
MaWin schrieb:> Dirk schrieb:>> Pickle hatte ich mir schon angeschaut, aber wenn ich es richtig verstehe>> geht nur JSON und ich nicht C-Datentypen.>> Es geht alles, was Python ist.
Leider nicht. Es gibt ein externes Modul namens "dill", das deutlich
mehr kann als das Standardmodul "pickle", aber zum Beispiel mit
Generatoren kann dill leider auch nicht umgehen.
Mußt Du nicht, sowas einfaches können das Standardmodul "pickle" und das
externe Modul "dill" problemlos händeln... und beide sind im
Zweifelsfall schneller, effizienter und fehlerärmer als etwas
Selbstgebasteltes.
Irgendwie finde ich keine Information das Pickle & Dill C Typen (uint8,
uint16, etc.) unterstützt.
>dill can pickle the following standard types:>>none, type, bool, int, long, float, complex, str, unicode,
Dirk schrieb:> Irgendwie finde ich keine Information das Pickle & Dill C Typen (uint8,> uint16, etc.) unterstützt.
Was soll dieser Satz überhaupt für ein Python-Programm bedeuten?
Kannst du bitte mal ein Beispiel liefern?
Dirk schrieb:> Irgendwie finde ich keine Information das Pickle & Dill C Typen (uint8,> uint16, etc.) unterstützt.
Irgendwie sehe ich in Deinem Code gar keine "C Typen". Kannst Du bitte
mal kurz erklären, was Du vorhast, was Du mit diesen "C Typen" überhaupt
tun willst, und warum Du "pickle" und / oder "dill" nicht einfach mal
(mit Deinen eigenen Klassen oder Instanzen) ausprobierst? Das kostet nur
zwei, drei Zeilen Code...
>Irgendwie sehe ich in Deinem Code gar keine "C Typen". Kannst Du bitte>mal kurz erklären, was Du vorhast, was Du mit diesen "C Typen" überhaupt>tun willst, und warum Du "pickle" und / oder "dill" nicht einfach mal>(mit Deinen eigenen Klassen oder Instanzen) ausprobierst? Das kostet nur>zwei, drei Zeilen Code...
Da hast Du natürlich recht. Ich muss die einzelnen Variablen in C Typen
(int8, uint8, int16, uint16, uint32, int32, uint64, int64) serialisieren
und dann per TCP an eine nicht veränderbare Anwendung senden, deshalb
scheint mir Pickle und Dill keine alternative zusein. Ich benutze dafür
momentan das Struct Modul und wollte nun erkennen ob die Variablen aus
der Childklasse kommen oder Parentklasse, weil ich entsprechend der
Klasse noch ein paar zusätzliche Daten mitgeben muss.
Dirk schrieb:> Da hast Du natürlich recht. Ich muss die einzelnen Variablen in C Typen> (int8, uint8, int16, uint16, uint32, int32, uint64, int64) serialisieren> und dann per TCP an eine nicht veränderbare Anwendung senden,
Bitte sei nicht böse, wenn ich Dir an dieser Stelle eine kleine
Ermahnung leider nicht ersparen kann: wenn Du das von vorneherein gesagt
hättest, dann hätte Dir zweifellos viel schneller geholfen werden
können.
> deshalb scheint mir Pickle und Dill keine alternative zusein.
Das stimmt, Pickle und Dill benutzen ein eigenes Datenformat, das für
den Austausch von Daten mit anderen Programmiersprachen nicht vorgesehen
ist.
> Ich benutze dafür> momentan das Struct Modul und wollte nun erkennen ob die Variablen aus> der Childklasse kommen oder Parentklasse, weil ich entsprechend der> Klasse noch ein paar zusätzliche Daten mitgeben muss.
Ja, das stimmt, struct.Struct ist eine Möglichkeit. Allerdings... wenn
ich das richtig verstehe, muß ohnehin jede Deiner Klassen wissen, welche
Eigenschaft sie bei der Serialisierung in welchen C-Datentyp umwandeln
muß. Denn ein int() in Python kann ja viele C-Datentypen behinahlten,
von einer uint8 bis hin zu einer int64. Insofern verstehe ich noch nicht
ganz, was dann die dynamische Auflösung bewirken soll. Zudem brauchst Du
die Information, welche Eigenschaft zu welcher Klasse gehört, ja nur bei
der Deserialisierung, aber die macht doch in Wahrheit Dein
unveränderliches Serverprogramm?
Wie dem auch sei, möchte ich an dieser Stelle ein etwas... unbekanntes
Feature von Python erinnern -- das "magische" Klassenattribut
"__slots__". Das bewirkt, daß die Autovivikation einer Klasse
ausgeschaltet wird und der Klasse nurmehr Eigenschaften zugewiesen
werden können, die in "__slots__" aufgeführt sind:
1
class A(object): pass
2
a = A()
3
a.eins = 1
4
a.zwei = 2
ist valider Python-Code und funktioniert natürlich genau so wie
erwartet: durch die Zuweisung an a.eins wird eine Eigenschaft "eins" in
der Instanz "a" unserer Klasse erzeugt und ihr der Wert 1 zugewiesen.
Dasselbe funktioniert übrigens nicht nur mit Instanz-, sondern auch mit
Klasseneigenschaften...
Genau dem kann man entgegenwirken, indem man der Klasseneigenschaft
"__slots__" eine Liste mit erlaubten Namen von Eigenschaften zuweist:
1
class A(object): __slots__ = ['eins']
2
a = A()
3
a.eins = 1 # erlaubt: "eins" ist in "__slots__"
4
a.zwei = 2 # Huch: ein Fehler!
Eigentlich ist "__slots__" zur Verbesserung der Speichereffizienz
gedacht und sollte nicht für Einschränkungen benutzt werden, aber...
genau: "sollte nicht" heißt nicht "darf nicht". Zu Beachten ist
allerdings, daß die Klasseneigenschaft "__slots__" nicht automatisch
vererbt wird, siehe dazu auch das kleine Skript im Anhang mit einer
generischen Serialisierungsmethode -- YMMV, HTH.
Nebenbei möchte ich aber noch auf zwei andere Ideen hinweisen, wie sich
das von Dir gewünschte bewerkstelligen ließe. Zum Einen wäre das ein
kompiliertes Shared Object (Win: DLL), das seine Parameter über das
Python-Standardmodul "ctypes" übergeben bekommt und dann wahlweise die
serialisierte Bytefolge zurückgibt oder ihrerseits direkt den Server
anspricht.
Eine andere Möglichkeit wäre es -- wenn Du etwas auf dem Server
installieren kannst -- einen kleines Reverse-Proxy-Programm zu
schreiben, das die Daten in einem beliebigen Format wie zum Beispiel
JSON entgegennimmt, passend wandelt, und sie dann über localhost an Dein
Serverprogramm übergibt. Der Vorteil wäre, daß Deine Client-Logik recht
einfach gehalten werden kann und Du die Daten einfach mithilfe des
json-Moduls von Python serialisieren, mit einem der Standardmodule
urllib oder urllib3 oder dem Erweiterungsmodul requests per HTTP an
einen simplen HTTP-Server auf der Basis von Flask oder
http.server.HTTPServer übergeben kannst.
Zuletzt möchte ich noch auf zwei Kleinigkeiten hinweisen, die mir an
Deinem Code aufgefallen sind, nämlich die "magische" Methoden __repr__()
und __str__() sowie die Abfrage, ob Dein Code als Programm oder als
Modul geladen worden ist.
Die magische Methode __str__() wird immer dann aufgerufen, wenn Deine
Instanz als String repräsentiert werden soll. Die Funktion print() macht
das automatisch, ebenso eine explizierte Konvertierung mit str(). Die
magische Methode __repr__() tut etwas sehr Ähnliches, soll aber eine
String-Repräsentation der Instanz zurückgeben, aus der die Instanz
wiederhergestellt werden kann -- in der Praxis funktioniert das
allerdings leider häufig nicht so gut. __repr__() wird auch immer dann
aufgerufen, wenn eine __str__()-Methode nicht vorhanden ist oder wenn
diese Repräsentation explizit mit der Builtin-Funktion repr()
angefordert wird.
Die Codezeile
1
if __name__ == '__main__':
fragt ab, ob Dein Code als Programm ablaufen soll -- also: mit
./programm.py gestartet wurde. Wenn dies der Fall ist, wird der Code im
If-Block ausgeführt. Sofern Dein Code hingegen von einem anderen
Python-Programm mit "import" oder einem ähnlichen Mechanismus geladen
wurde, wird der Code nicht ausgeführt. Das ist aus zweierlei Gründen
praktisch: einerseits kannst Du in dem if-Block ganz einfach simple
Unittests und / oder Beispielcode unterbringen, andererseits kannst Du
Deine Klassen und Funktionen von anderem Python-Code aus laden und
benutzen, ohne daß der If-Block ausgeführt wird. Ich zum Beispiel nutze
dieses Feature gerne in Verbindung mit Datenbanken und den OR-Mappern
SQLObject und SQLAlchemy: wird mein Code als Programm ausgeführt, werden
die Tabellen alle gelöscht, neu angelegt, und Beispieldaten zum Testen
eingetragen, während mein Code als Modul geladen, stellt er die
Datenbankabstraktion zur Verfügung.
Edit: Anhang vergessen... :-)
>Bitte sei nicht böse, wenn ich Dir an dieser Stelle eine kleine>Ermahnung leider nicht ersparen kann: wenn Du das von vorneherein gesagt>hättest, dann hätte Dir zweifellos viel schneller geholfen werden>können.
Bin ich nicht. Ich hatte mich etwas zu stark versteift auf mein Problem
ohne das drum herum zuerklären.
Ich muss die Daten nicht nur serialsieren, sondern auch empfangende
Bytearrays wieder deserialsieren zu der entsprechen Klasse. Der Tipp mit
den _slots_ und mro sind schon sehr hilfreich und wie erwähnt für eine
Klasse welche keine Vererbung hat klappt es auch wunderbar. Jetzt heisst
es die Vorschläge genauer zu verstehen und ein Testprojekt zuerstellen.
Python bietet extrem viele Möglichkeiten, aber mir fehlt ein bisschen
wie bei Qt das Metasystem, weil darüber kommt man relativ leicht an die
benötigten Information, aber anscheinend bietet Python hier auch schon
einiges.
Vielleicht kannst du dir dann eines der gängigen
Serialisierungsframeworks anschauen wie Protobuf, Flatbuf oder Cap'n
Proto. Die kannst du mit allen möglichen Sprachen verwenden.
Dirk schrieb:> Bin ich nicht.
Prima, Danke! :-)
> Ich muss die Daten nicht nur serialsieren, sondern auch empfangende> Bytearrays wieder deserialsieren zu der entsprechen Klasse. Der Tipp mit> den _slots_ und mro sind schon sehr hilfreich und wie erwähnt für eine> Klasse welche keine Vererbung hat klappt es auch wunderbar. Jetzt heisst> es die Vorschläge genauer zu verstehen und ein Testprojekt zuerstellen.
Hmmm... also, ehrlich gesagt habe ich die Befürchtung, daß wir bezüglich
meines Hinweises auf die Detailtiefe noch nicht wirklich weitergekommen
sind. Klar, Du möchtest es verstehen, und ich möchte Dir wirklich gerne
helfen, aber hier sind noch so viele Fragezeichen... Ich frag' einfach
mal so ein paar Dinge ins Blaue hinein, die mich interessieren würden,
okay? Wenn Du zu irgendeinem Teil davon nichts sagen kannst oder
möchtest, ist das natürlich völlig okay, aber mit mehr Informationen
hätte ich vielleicht noch mehr gute Ideen...
Du schreibst weiter oben, daß es eine Gegenseite mir einer "nicht
veränderbaren Anwendung" gibt. Je genauer Du sagen kannst, was für eine
Anwendung das ist und welche Art von Daten übertragen und wie sie
serialisiert werden, desto besser können wir helfen. Zudem verstehe ich
immer noch nicht, warum Du unbedingt Vererbung benutzen und Deine
Attribute dynamisch auflösen willst. Reichen da nicht einfache
Kompositionen oder Mixins? Und: wenn die Anwendung ohnehin am Netzwerk
lauscht und darüber kommuniziert, nutzt sie dann vielleicht schon ein
fertiges Serialisierungsframework wie eines der von Rolf Magnus
genannten, eins von PHP oder Perl oder etwas wie Boost::Serialization
für C++?
Dem schließt sich vor allem auch wegen des Hintergrundes Deines
Beispielcodes auch die Frage an, wie Zeichenketten und andere
Array-Typen übergeben werden; da gibt es ja eine Reihe von Möglichkeiten
für die Spezifikation der Länge. Einige Serialisierungsmöglichkeiten,
die ich kenne -- C, CSV und das interne Format von PostgreSQL zum
Beispiel -- nutzen dort eine Art Feldtrenner, C zum Beispiel das
abschließende Null-Byte, während andere die Länge als Prefix oder als
Teil eines Formatstrings übergeben. Wie macht Dein "Server" das?
> Python bietet extrem viele Möglichkeiten, aber mir fehlt ein bisschen> wie bei Qt das Metasystem, weil darüber kommt man relativ leicht an die> benötigten Information, aber anscheinend bietet Python hier auch schon> einiges.
Gerade in der Metaprogrammierung ist Python sehr stark, weil im Grunde
fast alles in Python -- Klassen, Methoden, Funktionen -- wiederum
Python-Objekte sind.
Im Anhang findest Du zwei Skripte: i.py zeigt ein bisschen
Metaprogrammierung bei der dynamischen Erzeugung einer Klasse; das dort
gezeigte benutze ich gerne im Zusammenhang mit dem Framework WTForms für
HTML-Formulare. d.py dagegen zeigt fünf Python-Features, die dynamisch
arbeiten:
1. Das "dataclasses"-Modul mit dem Decorator "@dataclass", der anhand
der angegebenen Klassenattribute passende "magische" Methoden wie
Konstruktor (_init_), Gleichheitsoperator (_eq_), Repräsentator
(_repr_) etc. -- sofern die Klasse sie nicht bereits selbst definiert.
Sehr spannend auch _hash_, das eine Klasse zu einem "Hashable" macht,
womit sie zum Beispiel als Schlüssel in einem Dictionary (dict(), {})
genutzt werden kann.
2. Den Deskriptor "YearDesc", der eine Klassenvariable beschreibt (und
in diesem Falle auch einschränkt). Deskriptoren können (normalerweise...
auch da gibt es ein paar nifty Tricks) nur auf Klassenvariablen
angewendet werden, wie in den Klassen "Student" und "Graduate". Sie
"beschreiben" eine Variable, oder genauer die Operationen "__get__" und
"__set__", die in meinem Beispielskript verwendet werden, um
sicherzustellen, daß die zugewiesenen Werte wirklich Integers sind.
3. Die Funktion "validate_positive", die dem Konstruktor des Deskriptors
YearDesc als Mitglied einer Liste von ausführbaren First Class Objects
übergeben wird, um sicherzustellen, daß die übergebene Zahl positiv ist
-- die Zahl könnte auch ein Float oder Complex oä. sein.
4. Eine Klasse mit der "magischen" Methode "__call__", deren Instanzen
darum ausführbar sind, ähnlich wie bei functools.partial -- nur mit
wesentlich mehr Kontrolle. Im Beispielskript benutze ich diese Klasse
ebenfalls als Validator, welcher sicherstellt, daß der übergebene Wert
sich im Bereich zwischen "mini" und "maxi" befindet. Bitte beachte, wie
die Klasse in der __set__-Methode die ihrem Konstruktor übergebenen
Validatoren aufruft.
Das Skript "d.py" ist sicherlich eher was für fortgeschrittene
Pythonistas, aber es zeigt ein paar etwas esoterischere Features, die
für Deinen Anwendungsfall möglicherweise nützlich sind. Wenn Du Fragen
hast, dann kannst Du natürlich gern hier im Forum fragen, aber wenn Du
magst, freue ich mich auch über eine PN. Bitte beachte allerdings, daß
ich meine privaten E-Mails bei Weitem nicht so häufig lese, wie ich das
tun sollte... ;-)
Erstmal Danke Sheeva für deine Hilfe.
>Du schreibst weiter oben, daß es eine Gegenseite mir einer "nicht>veränderbaren Anwendung" gibt. Je genauer Du sagen kannst, was für eine>Anwendung das ist und welche Art von Daten übertragen und wie sie>serialisiert werden, desto besser können wir helfen. Zudem verstehe ich>immer noch nicht, warum Du unbedingt Vererbung benutzen und Deine>Attribute dynamisch auflösen willst. Reichen da nicht einfache>Kompositionen oder Mixins? Und: wenn die Anwendung ohnehin am Netzwerk>lauscht und darüber kommuniziert, nutzt sie dann vielleicht schon ein>fertiges Serialisierungsframework wie eines der von Rolf Magnus>genannten, eins von PHP oder Perl oder etwas wie Boost::Serialization>für C++?
Ob ich den Namen der Applikation auf dem Server nennen kann weiss ich
nicht, aber wie die Applikation genau die Daten serialisiert und
deserialisert kann ich Dir nicht sagen, weil ich es selber nicht weiss,
aber ist kein Protobuf, QDatastream oder Json. Du kannst Dir per Grafik
die Daten zusammenstellen, welche als C/C++ Typen übertragen werden. Wie
z.B. Temperatur, Tag, Monat, Jahr. Die Temperatur = int8 in Celsius, Tag
= uint8, Monat = uint8 und Jahr uint16. Zusätzlich wird dem Daten eine
feste Identifikation zugewiesen, damit Server und Client weiss um welche
Daten es sich handelt. Welche Daten ausgetauscht werden ist dem Anwender
überlassen. Die Daten werden mit Protokoll drumherum versendet oder
empfangen. Zu dem Datenaustausch gibt es noch weitere Befehle, aber die
sind hier uninteressant. Natürlich wird auch die Byteorder, Anfang der
Nachricht und Länge in dem Protokol geachtet. Array-Typen hab ich
gerade nicht im Kopf und wenn ich mich richtig erinnere werden Strings
Null-terminiert.
Wieso ich gerne Vererbung nutzen möchte liegt daran, dass es sich
anbietet meiner Meinung nach anbietet.
Ein technisches Beispiel:
Ein Multimeter kann Gleichspannung, Wechselspannung, Gleichstrom und
Wechselstrom messen. Ein Oszilloskop kann unter gewissen Umständen auch
die Messungen tätigen und noch zusätzlich die Frequenz messen. Jetzt
macht es für mich Sinn, dass das Oszilloskop die Eigenschaften und
Methoden des Multimeters vererbt bekommt. Natürlich könnte ich auch zwei
Klassen erstellen und beide Klassen besitzen identische Eigenschaften
und Methoden, falls sich nun etwas ändert muss ich beide Klassen pflegen
und nicht nur eine. Die Vererbung bietet sich in solchen Fällen an oder
sehe ich das falsch?
Der Dekorator @dataclass ist mir bekannt und auch Methodendekoratoren,
welche ich für die Validierung nutzen würde.
Hier ein Beispiel in Qt ohne Metainformationen und ohne Veerbung:
Eine Klasse mit zwei Q_PROPERTY, aber es können noch 60000k weitere
Eigenschaften ausgetauscht werden, dann schreibst Du Dir hier ein Wolf
um die Daten zu serialsieren.
1
QByteArrayMeinedaten::Serialze()
2
{
3
QByteArrayba;
4
QDataStreamout(&ba,QIODevice::WriteOnly);
5
out<<m_temperatur;
6
out<<m_jahr;
7
// 60000 andere Werte (int8/uint8)
8
9
returnba;
10
}
Aus diesen Grund würde ich eher das Metasystem nutzen und über alle
Eigenschaften iterieren die Datentype erkennen und in das Bytearray
ablegen.
Jetzt kommen wir auf das Problem im Eingangspost zurück. Ich muss jeder
Klasse die Identifikation mitgeben, ansonsten weiss der Server oder
Client nicht zu welcher Klasse die Daten gehören. Wenn ich nun alle
Eigenschaften der Klasse und der Childklasse auslese mit
[c]
# hier bekomme ich alle Variablen
a = vars(x)
print(a)
# Wie kann ich hier prüfen von welcher Klasse _firstname kommt?
[\c]
Dann weiss ich nicht aus welcher Klasse die Daten kommen. Also müsste
ich den Objektbaum erhalten, dann bei jeder Klasse die Properties
ermitteln, entsprechend der Datentype (uint8, int8, uint16, int16, ... )
in Bytes wandeln und die Identifikation anhängen. In dem
Eingangsbeispiel hat die Person die Identifkation 1 und der Student die
Identifikation 2. Serialsiere ich sturr die Variablen des Studenten in
das Bytearray und hänge die Identifikation 2 an, dann wird die
Applikation ein Fehler werfen, weil es zwei getrennte Datenpakete sind.
Dirk schrieb:> Erstmal Danke Sheeva für deine Hilfe.
Sehr gerne, dafür ist so ein Forum ja da... ;-)
> Ob ich den Namen der Applikation auf dem Server nennen kann weiss ich> nicht, aber wie die Applikation genau die Daten serialisiert und> deserialisert kann ich Dir nicht sagen, weil ich es selber nicht weiss,> aber ist kein Protobuf, QDatastream oder Json. Du kannst Dir per Grafik> die Daten zusammenstellen, welche als C/C++ Typen übertragen werden. Wie> z.B. Temperatur, Tag, Monat, Jahr. Die Temperatur = int8 in Celsius, Tag> = uint8, Monat = uint8 und Jahr uint16. Zusätzlich wird dem Daten eine> feste Identifikation zugewiesen, damit Server und Client weiss um welche> Daten es sich handelt. Welche Daten ausgetauscht werden ist dem Anwender> überlassen. Die Daten werden mit Protokoll drumherum versendet oder> empfangen. Zu dem Datenaustausch gibt es noch weitere Befehle, aber die> sind hier uninteressant. Natürlich wird auch die Byteorder, Anfang der> Nachricht und Länge in dem Protokol geachtet. Array-Typen hab ich> gerade nicht im Kopf und wenn ich mich richtig erinnere werden Strings> Null-terminiert.
Hm, mit den nullterminierten Strings könnte das mit struct.unpack() ein
wenig schwierig werden, denn für dessen Formatstring mußt Du natürlich
vorher wissen, wie lang der String ist. Das sollte aber machbar sein,
wenn Du die Struktur einer Nachricht kennst und darüber iterieren
kannst... am Ende sind bytes() und str() unter Python ja auch
iterierbar. Cooler wäre es natürlich, wenn der Server etwas mitlieferte,
aus dem Du Deinen Formatstring machen kannst...
Aber nochmal zurück auf Anfang, nur um abzuklären, daß ich Deinen
Anwendungsfall korrekt verstanden habe. Also, da gibt es eine
Serveranwendung, der Du über ein Webinterface oder eine Clientsoftware
ein Objekt schicken und sagen kannst "gib mir mal diese und jene Daten"
-- also quasi so eine Art Datenbanksoftware. Dein Server sucht die Daten
dann zusammen, packt sie in ein serialisiertes Format in Form eines
Bytestrings, und die Clientsoftware, etwa eine Website klamüsert die
serialisierten Daten nach dem Empfang wieder auseinander und... macht
irgendwas damit, stellt sie also tabellarisch dar, visualisiert sie,
speichert sie ab oder so etwas in dieser Art. Ist das grob richtig? ;-)
> [siehe unten]> Ein technisches Beispiel:>> Ein Multimeter kann Gleichspannung, Wechselspannung, Gleichstrom und> Wechselstrom messen. Ein Oszilloskop kann unter gewissen Umständen auch> die Messungen tätigen und noch zusätzlich die Frequenz messen. Jetzt> macht es für mich Sinn, dass das Oszilloskop die Eigenschaften und> Methoden des Multimeters vererbt bekommt. Natürlich könnte ich auch zwei> Klassen erstellen und beide Klassen besitzen identische Eigenschaften> und Methoden, falls sich nun etwas ändert muss ich beide Klassen pflegen> und nicht nur eine. Die Vererbung bietet sich in solchen Fällen an oder> sehe ich das falsch?
Ich würde da eher auf Komposition statt auf Vererbung gehen, aber nach
meinem bisherigen Eindruck wäre es vermutlich sinnvoller, Deine Klassen
dynamisch zu erzeugen. Vererbung und sogar eine Komposition bieten Dir
an dieser Stelle keine realen Vorteile. Dafür handelst Du Dir aber einen
wesentlichen Nachteil ein, denn Du verlierst dadurch an Flexibilität.
Insofern würde ich -- wenn ich das alles richtig verstanden habe,
natürlich -- nur für das "äußere" Format, also quasi den
gleichbleibenden Briefumschlag der Nachrichten (wie zum Beispiel die von
Dir genannte Identifikation) die Vererbung einsetzen.
Im Anhang findest Du mit "e.py" ein kleines Skript, daß die Datenklassen
als @dataclass dynamisch erzeugt. Das hat ein paar kleine Vorteile: zum
Einen hast Du dann tatsächlich eine Art "Metasystem", aus dem Du
Informationen abfragen kannst, wie die Methode BaseData.getField()
zeigt. Zum Anderen kannst Du die festen Teile Deiner Servernachrichten
in einer Basisklasse (hier: BaseData) anlegen und Deine dynamischen
Daten dann relativ einfach hinzufügen. Tatsächlich kannst Du dafür sogar
Formatstrings für __repr__() und Deine Serialisierung / Deserialisierung
mithilfe von struct.[un]pack() dynamisch zusammenbauen. Ich würd mir
vielleicht ein enum.Enum dafür bauen, dessen Werte jeweils ein
collections.namedtuple() mit den Eigenschaften "format_pack",
"format_unpack", und "field" für den passenden Deskriptor wären... oder
vielleicht auch ein dict() statt eines Enum. Damit kann ich dann über
die Felder der erwarteten Nachricht iterieren und mir sowohl die
Formatstrings für die (De-)Serialisierung als auch die Deskriptoren
ziehen, und daraus dann die zu erwartende Nachrichtenklasse dynamisch
aufbauen. Vielleicht würde ich auch noch ein Feld "methods" in mein
namedtuple() mit Methoden hinein bauen, die mein dynamisch gebaute
Klasse am Ende erhalten soll.
Tipp am Rande: mit type() können ebenfalls Klassen dynamisch erzeugt
werden, das ist meines Wissens der Mechanismus, den
dataclasses.make_dataclass() benutzt.
> Der Dekorator @dataclass ist mir bekannt und auch Methodendekoratoren,> welche ich für die Validierung nutzen würde.
Naja, in meinem Beispiel d.py benutze ich keine Methodendekoratoren,
sondern Deskriptoren für die Validierung. Das erscheint mir ein bisschen
flexibler zu sein als zum Beispiel der Standard-Dekorator @property, der
intern übrigens seinerseits ebenjene Deskriptoren benutzt... ;-)
> Aus diesen Grund würde ich eher das Metasystem nutzen und über alle> Eigenschaften iterieren die Datentype erkennen und in das Bytearray> ablegen.
Najaaa... also so ein Metasystem wie jenes von Qt gibt es in Python
meines Wissens nicht, aber ich kenne mich mit Qt nicht mehr sonderlich
gut aus -- zuletzt habe ich mal eine GUI-Applikation mit PerlQt
entwickelt, allerdings dürfte das jetzt auch schon wieder lockere
fünfzehn Jahre her sein... Also sollten wir lieber davon ausgehen, daß
ich von Qt keine Ahnung habe. ;-)
> [c]> # hier bekomme ich alle Variablen> a = vars(x)> print(a)> # Wie kann ich hier prüfen von welcher Klasse _firstname kommt?> [\c]
Naja... im Grunde genommen kannst Du Dich anhand der Method Resolution
Order (mro, wie oben bereits erwähnt) an den Parent-Klassen entlang
hangeln und für jede davon schauen, ob Deine Eigenschaft dort vorhanden
ist. Die letzte Klasse, in welcher Deine Eigenschaft vorhanden ist, ist
dann jene, die sie enthält.
Und, nebenbei: mit vars() bekommst Du NICHT alle Variablen, sondern nur
die in <instanz>.__dict__ -- die in <instanz>.__slots__ und
<klasse>.__slots__ nicht. Wirklich alle Variablen, auch die "privaten"
(oder besser: nach der Konvention "versteckten") bekommst Du mit
1
{i: getattr(obj, i) for i in dir(obj)}
-- und natürlich kannst Du da dann wie in jeder Comprehension auch
filtern, was genau Du haben möchtest. Das ist deswegen wichtig, weil zum
Beispiel bei diesen @dataclass-Klassen ein ganzer Haufen der Magie sich
in versteckten Attributen wie _dataclass_fields_ abspielt und Du die
dort enthaltenen Informationen quasi als Metasystem benutzen kannst,
ohne die Eigenschaften Deiner Klasse zuvor aufwändig auszeichnen und /
oder hinterher aufwändig filtern zu müssen. HTH, YMMV... ;-)
>Hm, mit den nullterminierten Strings könnte das mit struct.unpack() ein>wenig schwierig werden, denn für dessen Formatstring mußt Du natürlich>vorher wissen, wie lang der String ist. Das sollte aber machbar sein,>wenn Du die Struktur einer Nachricht kennst und darüber iterieren>kannst... am Ende sind bytes() und str() unter Python ja auch>iterierbar. Cooler wäre es natürlich, wenn der Server etwas mitlieferte,>aus dem Du Deinen Formatstring machen kannst...
Die Information der Länge von Strings ist im Protokol behandelt, somit
kein Problem.
>Aber nochmal zurück auf Anfang, nur um abzuklären, daß ich Deinen>Anwendungsfall korrekt verstanden habe. Also, da gibt es eine>Serveranwendung, der Du über ein Webinterface oder eine Clientsoftware>ein Objekt schicken und sagen kannst "gib mir mal diese und jene Daten">-- also quasi so eine Art Datenbanksoftware. Dein Server sucht die Daten>dann zusammen, packt sie in ein serialisiertes Format in Form eines>Bytestrings, und die Clientsoftware, etwa eine Website klamüsert die>serialisierten Daten nach dem Empfang wieder auseinander und... macht>irgendwas damit, stellt sie also tabellarisch dar, visualisiert sie,>speichert sie ab oder so etwas in dieser Art. Ist das grob richtig? ;-)
Fast, es ist keine Webapplikation mit Restinterface, sondern eine
Applikation, welche per TCP gesteuert und Daten ausgetauscht wird
wwischen dem Server und den Client.
Mit deinen Beispielen bin ich jetzt auch ein Schritt weitergekommen. Ich
hab erstmal auf die Vererbung verzichtet und bin wie empfohlen über
Komposition gegangen. Jetzt gefällt mir das alles schon viel besser und
spart viel Arbeit, wenn Variablen in der Klasse dazukommen oder
wegfallen. Vorher war es echt harte Fleissarbeit die Klassen,
Serialisierung und Deserialsierung anzupassen. Jetzt muss ich nur die
Klasse anpassen und der Rest passiert dynamisch :)
Besten Dank für die Hilfe!
Dirk schrieb:> Mit deinen Beispielen bin ich jetzt auch ein Schritt weitergekommen. Ich> hab erstmal auf die Vererbung verzichtet und bin wie empfohlen über> Komposition gegangen. Jetzt gefällt mir das alles schon viel besser und> spart viel Arbeit, wenn Variablen in der Klasse dazukommen oder> wegfallen. Vorher war es echt harte Fleissarbeit die Klassen,> Serialisierung und Deserialsierung anzupassen. Jetzt muss ich nur die> Klasse anpassen und der Rest passiert dynamisch :)
Cool, das freut mich sehr, daß ich Dir helfen konnte.
> Besten Dank für die Hilfe!
Sehr gerne, und Dir vielen Dank für die Rückmeldung! ;-)