Forum: PC-Programmierung Python Variable von Childclass


von Dirk (Gast)


Lesenswert?

Hallo,

kann man herausfinden, ob eine Variable von der Childclass oder Parent 
kommt?
1
#!/usr/bin/env python3
2
3
class Person:
4
  def __init__(self, fname, lname):
5
    self._firstname = fname
6
    self._lastname = lname
7
8
  def printname(self):
9
    print(self._firstname, self._lastname)
10
11
class Student(Person):
12
  def __init__(self, fname, lname, year):
13
    super().__init__(fname, lname)
14
    self._graduationyear = year
15
16
  def welcome(self):
17
    print("Welcome", self._firstname, self._lastname, "to the class of", self._graduationyear)
18
19
x = Student("Mike", "Olsen", 2019)
20
x.welcome()
21
x.printname()
22
# hier bekomme ich alle Variablen
23
a = vars(x)
24
print(a)
25
# Wie kann ich hier prüfen von welcher Klasse _firstname kommt?

von MaWin (Gast)


Lesenswert?

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.

von Gerd A. (gerd_a289)


Lesenswert?

Hi Dirk,
du bekommst zwar mit str(x.printname) heraus wo deine methode verortet 
ist, aber wo deine variable angesiedelt ist ist wegen des eigenen 
namespaces scheinbar nicht ermittelbar, siehe:

https://stackoverflow.com/questions/3648564/python-subclass-access-to-class-variable-of-parent

Gruß Gerd

von Dirk (Gast)


Lesenswert?

>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.

von MaWin (Gast)


Lesenswert?

Dirk schrieb:
> serialzieren

Ok. Das ist eines der wenigen Einsatzzwecke von mro().
Allerdings gibt es bereits einen eingebauten (de-)serialisierer. Er 
heißt pickle.

von Dirk (Gast)


Lesenswert?

Pickle hatte ich mir schon angeschaut, aber wenn ich es richtig verstehe 
geht  nur JSON und ich nicht C-Datentypen.

von MaWin (Gast)


Lesenswert?

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.

von Nur_ein_Typ (Gast)


Lesenswert?

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.

von Nur_ein_Typ (Gast)


Lesenswert?

Dirk schrieb:
>
1
> # hier bekomme ich alle Variablen
2
> a = vars(x)
3
>

Nein, so bekommst Du nur die Variablen aus x.__dict__. Alle 
Variablennamen kannst Du mit dir(x) holen und dann ein dict() draus 
machen:
1
{k: getattr(x, k, None) for k in dir(x)}

>
1
> print(a)
2
> # Wie kann ich hier prüfen von welcher Klasse _firstname kommt?
3
>

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.

von Dirk (Gast)


Lesenswert?

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,

von MaWin (Gast)


Lesenswert?

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?

von Nur_ein_Typ (Gast)


Lesenswert?

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...

von Dirk (Gast)


Lesenswert?

>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.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

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... :-)

: Bearbeitet durch User
von Dirk (Gast)


Lesenswert?

>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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

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... ;-)

von Dirk (Gast)


Lesenswert?

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:
1
#include <QObject>
2
#include <QDataStream>
3
4
class Meinedaten : public QObject
5
{
6
    Q_OBJECT
7
8
    Q_PROPERTY(qint8 temperatur READ temperatur WRITE setTemperatur NOTIFY temperaturChanged)
9
    Q_PROPERTY(quint16 jahr READ jahr WRITE setJahr NOTIFY jahrChanged)
10
11
public:
12
    explicit Meinedaten(QObject *parent = nullptr);
13
14
    qint8 temperatur() const;
15
    quint16 jahr() const;
16
    // 60000 andere Byte Daten
17
18
    QByteArray Serialze();
19
20
21
public slots:
22
    void setTemperatur(qint8 temperatur);
23
    void setjahr(quint16 jahr);
24
25
signals:
26
    void temperaturChanged(qint8 temperatur);
27
    void jahrChanged(quint16 jahr);
28
29
private:
30
    qint8 m_temperatur;
31
    quint16 m_jahr;
32
};

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
QByteArray Meinedaten::Serialze()
2
{
3
    QByteArray ba;
4
    QDataStream out(&ba, QIODevice::WriteOnly);
5
    out << m_temperatur;
6
    out << m_jahr;
7
    // 60000 andere Werte (int8/uint8)
8
9
    return ba;
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.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

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... ;-)

von Dirk (Gast)


Lesenswert?

>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!

von Sheeva P. (sheevaplug)


Lesenswert?

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! ;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.