Datei in XML-Format umwandeln

Code-Stücke können hier veröffentlicht werden.
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

Nach ehrfürchtigem Staunen über die vielen tollen Code-Beispiele in diesem Forum will ich nun den Schritt wagen, ein Progrämmle von mir auch mal zur Begutachtung freizugeben. Das Programm macht, was es soll, aber als Python-Dilettant bin ich mir bei vielem unsicher. Würde mich freuen, wenn jemand Lust hat, mal einen Blick darauf zu werfen und mir Feedback zu geben.

Am Programm kann man sicher erkennen, dass ich bei vielen Strukturierungs- und Benennungsfragen noch unsicher bin. Zum Beispiel:
  • - Wie grenze ich eine Klasse ab, wie eine Funktion?
    - Wann __-Konvention für Methodennamen, wann nicht?
    - In Klassen: wann mit self-Variablen arbeiten, wann mit lokalen?
    - ...
Mein Beispiel habe ich in einem Pastebin abgelegt. Es ist für die Veröffentlichung im Forum leicht modifiziert.
http://www.python-forum.de/pastebin.php?mode=view&s=100

Wenn Bedarf besteht, könnte ich auch Testdaten zur Verfügung stellen. Ich müsste nur wissen, wie das am besten geht.

Der Anwendungszusammenhang ist folgender:
Aus einer Finanz-DB werden Vermögenspositionen in Dateien exportiert, jeder Positionstyp in eine eigene Datei mit spezifischem Record-Format. Aus diesen Dateien müssen XML-Dateien erzeugt werden. Als Konstruktionsvorschrift für die XML-Dateien dient die erste Zeile der Export-Dateien. Sie enthält, gewissermassen als Spaltenüberschrift, für jedes Feld einen Pfad, aus dem die XML-Tags erzeugt werden können.

Vielen Dank für Eure Antworten!
BlackJack

@Jackaroo: Zuerst die direkten Fragen:

Klasse vs. Funktion: Klassen beschreiben "Dinge" und Funktionen und Methoden "Tätigkeiten". Das sollte sich in der Namensgebung wiederspiegeln und natürlich im Inhalt, also was der Quelltext anstellt.

`__`-Präfix: Wenn Du die Frage stellen musst, dann ist die Antwort wohl: Gar nicht. Dieser Präfix wurde eingeführt, um Namenskollisionen bei der Vererbung zu vermeiden und da insbesondere bei der Mehrfachvererbung, also wenn man Klassen entwirft die irgendwann einmal mit Klassen in einer Vererbungshierarchie landen, von denen man nichts weiss, und die auch von der eigenen Klasse nichts wissen. Solange man also keine Klasse entwirft, die als Mixin-Klasse verwendet werden soll, braucht man das nicht.

`self`: Klassen verbinden Daten und Funktionen die auf diesen Daten operieren zu einer Einheit. Die gemeinsamen Daten muss man an das Objekt binden, eben damit die anderen Methoden da heran kommen können. Alles was nur die Methode lokal benötigt, naja, ist eben lokal.

Zum Quelltext: Zur Namensgebung solltest du mal einen Blick in den Style Guide (a.k.a. PEP8) werfen. Für Konstanten GROSSBUCHSTABEN_UND_UNTERSTRICHE, Klassen in MixedCase und alles andere in klein_mit_unterstrichen.

Was dann auffällt ist, dass das Eingabeformat anscheinend CSV mit Semikolon als Trenner ist und das Ausgabeformat XML und Du das alles *selbst* erledigst. In der Standardbibliothek gibt es das `csv`-Modul und den `xml.etree.ElementTree.TreeBuilder`.

Die Klasse `ExportFile` ist mir vom Sinn her nicht so ganz klar. Wenn eine Klasse in der der `__init__()` alles erledigt, dann ist das oft ein Zeichen, dass man eigentlich mit einer Funktion auskommen würde. In diesem Fall zum Beispiel eine, die die erste Zeile und den Rest der Zeilen als Tupel zurück gibt.

Allerdings würde ich die Datei gar nicht komplett einlesen, sondern eher "lazy" verarbeiten. Wenn die erste Zeile die Spaltennamen enthält, ist ein `csv.DictReader` das Mittel der Wahl.

`Asset.tagFields()` ist sehr verwirrend. Wenn ich das richtig sehe hast Du versucht eine ganz einfach ``for``-Schleife möglichst kompliziert und undurchsichtig zu schreiben. ;-) Letztendlich ist das aber einfach nur: ``self.tagged_fields = zip(tags, self.fields)``. Wobei hier in einer Methode plötzlich ein neues Attribut auf dem Objekt erzeugt wird. Das ist IMHO schlechter Stil. Vom Lesen der `__init__()` sollte klar werden, welche Attribute ein Objekt besitzt, und nach Abarbeitung der `__init__()` sollte es in der Regel in einem benutzbaren Zustand sein. Wenn der Inhalt der Klasse letztendlich nur aus einem `split()` der Eingabezeile und einem `zip()` besteht, und das `split()` eigentlich schon vom `csv`-Reader übernommen wird, erscheint auch diese Klasse mit einer einfachen Funktion oder sogar komplett "inline" umgesetzt werden zu können.

Der Datentyp sollte in Namen möglichst nicht auftauchen. Wenn man zum Beispiel statt einer Liste irgendwann einmal einen anderen Typ verwendet, muss man entweder alle betroffenen Namen anpassen, oder man hat verwirrenden Quelltext.

`XMLOutput` beschreibt die Klasse nicht gut. `XMLWriter` trifft es genauer was die Klasse macht.

`writeFields()` ist für meinen Geschmack zu kompliziert. Ich hätte das umsetzen in eine verschachtelte Struktur und das Entfernen von leeren Unterstrukturen in zwei Schritte aufgeteilt und wohl rekursiv statt iterativ gelöst.

Zum Rest von der `XMLOutput` sage ich jetzt mal nichts, weil ich mich nicht durch die Logik arbeiten möchte, die man ohne konkrete Beispiele wahrscheinlich nur schwer versteht.

Ich bin kein grosser Fan von ``continue`` weil das eine Sprunganweisung ist, die mitten aus einem Block wieder an den Anfang springt und damit das ganze nicht so schön an der Einrückung ersichtlich ist. Ich hätte für das Hauptprogramm eher eine Funktion geschrieben, welche die Dateinamen filtert und dann über das Ergebnis die Schleife geschrieben. Oder den ``else``-Zweig weg gelassen und dafür den Rest des Schleifenköpers eine Ebene weiter eingerückt.

Zum Schluss ist dann wieder so eine komische ``while``-Schleife die man viel einfacher als ``for``-Schleife ausdrücken könnte.

Alles ab dem ``# MAIN``-Kommentar sollte in einer Funktion verschwinden. Auf Modulebene sollten nur Importe und Konstanten-, Funktions-, und Klassendefinitionen stehen. Und die Hauptfunktion kann man mit folgendem Idiom vor dem ausführen schützen, wenn man das Modul nur importiert statt es als Programm auszuführen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Dann kann man das Modul importieren und einzelne Funktionen und Klassen ausprobieren, oder wiederverwenden, oder Werkzeuge benutzen, die aus dem Quelltext API-Dokumentation extrahieren.
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

@BlackJack: Wow, vielen Dank für die ausführlichen Kommentare und die rasend schnelle Antwort.

Das gibt mir eine Menge Stoff für eine Überarbeitung. Werde also erstmal in Klausur mit mir gehen. Nach einem ersten Lesen läuten nun schon tausend Erkenntnisglocken.

Die zip-Funktion ist natürlich ein Volltreffer. Nur, wie kommt man darauf, welche Funktionen und Module es noch alles gibt. Vor lauter Suchen kommt man dann ja nicht zum Codieren. Deine Hinweise (zip, csv-Modul, ...) sind auch insofern sehr willkommen.

Stimmt: while-Schleifen sind doof.

Werde mich nach einer Bedenkzeit nochmal mit Anschlussfragen melden.

J.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Jackaroo hat geschrieben:Die zip-Funktion ist natürlich ein Volltreffer. Nur, wie kommt man darauf, welche Funktionen und Module es noch alles gibt. Vor lauter Suchen kommt man dann ja nicht zum Codieren. Deine Hinweise (zip, csv-Modul, ...) sind auch insofern sehr willkommen.
Dafür gibt es ein nettes kleines Werk: http://docs.python.org/index.html.
Du solltest vorallem mal das Tutorial zu Beginn überfliegen, dort werden schon einige wichtige Module und Funktionen genannt.

Zudem schadet es nie das Hirn einzuschalten :mrgreen:.
Man könnte ja darauf kommen, das es für solche umfangreichen Aufgaben schon was geschrieben wurde und darauf hin mal eine Suchanfrage zustellen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Xynon1 hat geschrieben:
Jackaroo hat geschrieben:Die zip-Funktion ist natürlich ein Volltreffer. Nur, wie kommt man darauf, welche Funktionen und Module es noch alles gibt. Vor lauter Suchen kommt man dann ja nicht zum Codieren. Deine Hinweise (zip, csv-Modul, ...) sind auch insofern sehr willkommen.
Dafür gibt es ein nettes kleines Werk: http://docs.python.org/index.html.
Du solltest vorallem mal das Tutorial zu Beginn überfliegen, dort werden schon einige wichtige Module und Funktionen genannt.
Ich muss auch sagen dass ich auf viele Tricks erst gestoßen bin weil jemand im Forum, meist BlackJack, das irgendwo benutzt hat.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@Leonidas
Geht mir doch auch nicht anders :D

Aber dennoch, schaden tut es sicher nicht.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

@Xynon1

Das ruft ja schon nach einer Antwort. Mir ist nicht ganz klar, warum immer wieder unterstellt wird, dass Python-Neulinge hier im Forum Fragen stellen, ohne sich vorher ausreichend selbst informiert zu haben oder bestimmte Grunddokumente angeschaut zu haben. Die Schwierigkeit besteht einerseits darin, die Fülle der existierenden Informationen nicht nur zu lesen, sondern auch praktisch umzusetzen. Selbst in einem Dokument vom begrenzten Umfang des Python-Tutorials stehen soviele Informationen, dass der durchschnittlich begabte Mensch in der Regel wahrscheinlich nicht alles sofort parat hat, wenn er ein Programm schreibt. Das muss mit derZeit wachsen.

Zum andern ist das Suchen nach Modulen und Zusatzbibliotheken zeitaufwendig und aufgrund der Fülle des Angebots auch nicht einfach. Für erfolgreiches Suchen ist sicher auch Erfahrung nötig. Ich selbst habe in den letzten Wochen für mich cx_oracle, paramiko und pycrypto gefunden und für kleinere Aufgaben angewendet. Aber der Zeitaufwand dafür war schon erheblich. Zu meiner Schande muss ich auch gestehen, dass ich auch den Style Guide schon gelesen habe, aber trotzdem von BlackJack auf nicht konforme Benamsungen hingewiesen wurde. Ich habe eben dem Satz
There are a lot of different naming styles. It helps to be able to
recognize what naming style is being used, independently from what they
are used for.
zuviel Bedeutung beigemessen und den Rest nicht genau genug gelesen. Aufgrund von BlackJacks Hinweisen habe ich nochmal einen Blick hinein geworfen und nachgelesen, auf was er mich hingewiesen hat.

Ich fände es besser, wenn nicht so schnell unterstellt würde, das Neulinge "dumm" oder "faul" sind :wink: .

Diese Diskussion kommt wohl daher: im Forum gibt es Teilnehmer, die viel "geben" und andere, die viel "nehmen". Also nicht zwischen allen findet ein Austausch auf gleicher Ebene statt. Meine Python-Kenntnisse sind z.B. viel zu bescheiden, um anderen substantielle Hinweise geben zu können. Wenn dieses Ungleichgewicht hier im Forum akzeptiert ist (ist es das?), dann muss man vielleicht nicht in allen Fällen auf das Tutorial verweisen.

Das waren 10kg Metadiskussion von mir.
Gruss
J.
lunar

@Jackaroo: Weder sind die Beiträge einzelner repräsentativ für das Forum als Ganzes, noch unterstellt jeder Hinweis auf die Dokumentation oder das Tutorial dem Gegenüber automatisch Dummheit oder Faulheit. Insbesondere hat Dir in dieser Diskussion niemand konkret unterstellt, Du wärst dumm oder faul.

Lies also bitte nicht mehr aus Beiträgen heraus als tatsächlich darin steht, und vermeide erst recht, die Beiträge einzelner zu „10kg Metadiskussion“ aufzublasen. Wenn Dir die Beiträge einzelner aus welchen Gründen auch immer nicht gefallen, dann kläre das bitte im Privaten.
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Jackaroo hat geschrieben:Ich fände es besser, wenn nicht so schnell unterstellt würde, das Neulinge "dumm" oder "faul" sind :wink: .
Ich denke mal, die Kritik ging auch nicht direkt an dich.

Schlimm sind "Programmierer" die
  • elementare Grundlagen nicht kennen, sich aber weigern, das Tutorial durchzuarbeiten, da sie ja ihrer Einschätzung nach schon alles können.
  • für jedes Kleinstproblem hier sofort angerannt kommen, statt auch nur ein einziges Mal in die Dokumentation zu schauen.
  • Tipps völlig ignorieren, per "Trial and Error" Änderungen am Code vornehmen und dann fragen, warum das immer noch nicht geht.
  • erst fragen, dann aber auf Hinweise eingeschnappt und beleidigt reagieren ("Ich programmiere schon seit 25 Jahren BASIC auf einem Zumbitsu 1900-Computer und kenne mich deshalb viel besser aus und weiß wie es richtig gemacht wird!").
  • nur behaupten, sie hätten Dinge getan oder überprüft, in Wirklichkeit aber zu faul dazu waren und nur ihre persönliche Einschätzung statt des realen Resultats angeben.
  • [ ... weitere Punkte können hier locker ergänzt werden ... ]
BlackJack

Ich denke es hat niemand Dummheit unterstellen wollen, aber ich kann verstehen, das der Hinweis es schade nie das Gehirn einzuschalten, durchaus als nicht ganz so nett gemeint aufgefasst werden kann.

Bei der Dokumentation von der Standardbibliothek finde ich es ganz nett, dass das Inhaltsverzeichnis die Module thematisch ordnet. Da hat es mir als Anfänger öfter mal was gebracht einfach mal so drüber zu schauen und mich zu fragen, ob davon irgend ein Modul mein Leben, bei den jeweils aktuellen Programmen die ich schrieb, leichter macht.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@Jackaroo
An der Stelle muss, bzw möchte ich auch nochmal klar stellen, das ich dies in keinster Weise "böse" gemeint habe.
Es war lediglich nochmal ein Verweis auf das Tutorial und das man auch nochmal eine Suchmaschiene benutzen kann, denn wie BlackJack schon gesagt hatte, kann einem dies das Programmieren schonmal erleichtern.

Ich möchte mich also Entschuldigen das ich es so flappsig Ausgedrückt habe, das diese Aussage trotz :mrgreen: so ernst aufgefasst würde, hätte ich nicht erwartet.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

@Xynon1

Kein Grund, sich zu entschuldigen. Ich bin und war auch gar nicht angekratzt und wollte nur meinen Punkt verdeutlichen, der Deinen Beitrag ausgelöst hat. Deshalb habe ich 10kg Text geschrieben, damit ich nicht falsch verstanden werde. Offenbar habe ich mich doch unklar ausgedrückt.

"dumm" und "faul" waren als Übertreibung gemeint, deshalb die Anführungszeichen und der :wink: .

Ich fände es tatsächlich besser, nicht so schnell aufs Tutorial zu verweisen. Aber das war eine schlichte Meinungsäusserung. Ich verstehe Euren Punkt.

Und nun zurück zu Python:

@BlackJack:
Allerdings würde ich die Datei gar nicht komplett einlesen, sondern eher "lazy" verarbeiten. Wenn die erste Zeile die Spaltennamen enthält, ist ein `csv.DictReader` das Mittel der Wahl.
Der csv.reader ist Klasse, der csv.DictReader auch. Nur das Dictionary, das er zurückgibt, hat den Nachteil aller Dictionaries, dass nämlich alle Einträge wild durcheinandergewürfelt werden. Beschränke mich daher auf den einfachen Reader.
BlackJack

@Jackaroo: Das Reader-Objekt selbst hat aber ein Attribut `fieldnames` mit einer Liste in der richtigen Reihenfolge.
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

Damit niemand glaubt, dass ich mich stillschweigend davongestohlen habe: ich werde jetzt zwei Wochen abwesend sein. In dieser Zeit kann ich dies Thema nicht weiterverfolgen bzw. bearbeiten. Ich habe aber schon fast alle Hinweise von BlackJack aufgegriffen und sehe wie mein Programm kürzer und verständlicher wird.

Also erstmal vielen Dank für die guten Tips. Nach der Weihnachtspause würde ich das Thema gerne nochmal aufgreifen.

Bis dann wünsche ich allen frohe Weihnachten!

J.
zappa
User
Beiträge: 26
Registriert: Samstag 19. März 2011, 22:31

Hi, bin gänzlich neu im Forum (und bei Python) und habe mir in den letzten Tagen einige Beiträge zu Gemüte geführt. Und ich muss schon sagen... krass. Tolle Beiträge, Tipps u.v.m
Und um den Mut nicht ganz zu verlieren würde mich interessieren, wie lange Leute wie BlackJack (und auch die anderen, die z.T. über 3000 Einträge haben) schon mit Python beschäftigt sind. Hab Ihr beruflich damit zu tun? oder ist das nur Hobby?
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

zappa hat geschrieben:Und um den Mut nicht ganz zu verlieren würde mich interessieren, wie lange Leute wie BlackJack (und auch die anderen, die z.T. über 3000 Einträge haben) schon mit Python beschäftigt sind. Hab Ihr beruflich damit zu tun? oder ist das nur Hobby?
Ich beschäftige mich seit IMHO gut 4 Jahren mit Python, komme aber im Beruf eher selten dazu, es einzusetzen.

Grundsätzlich habe ich über 25 Jahre Erfahrung mit Programmierung und über 20 Jahre mit echter Softwareentwicklung. Ich lese diverse Fachbücher und Magazine und bemühe mich dadurch nicht nur einigermaßen auf dem Laufenden zu bleiben, sondern auch neue Dinge zu lernen.
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

Nach langer, langer Zeit habe ich nun BlackJacks Empfehlungsliste abgearbeitet. Das Ergebnis kann angesehen werden unter
http://www.python-forum.de/pastebin.php?mode=view&s=226 . Vielleicht hat ja wieder jemand Lust, nochmal einen Blick auf die überarbeitete Version zu werfen. Das Programm macht immer noch das Gleiche und funktioniert auch nach der Überarbeitung noch.

@Black Jack: Deine Tips waren wirklich super. Vielen Dank nochmal. (Leider ist noch ein "continue" übrig geblieben.)

Natürlich haben sich beim Abarbeiten von BlackJacks Liste wieder neue Fragen gestellt:
  • - Habe ich das mit dem rekursiven Aufruf von write_asset() so richtig verstanden?
    - Was ist der Vorteil des rekursiven Aufrufs gegenüber einer Schleife? (Bin noch am Grübeln.)
    - Ist es nun besser Klassenvariablen zu verwenden oder lokale Variablen, die weitergereicht und zurückgegeben werden? (Hängt wahrscheinlich davon ab, ob ...)
J.
BlackJack

@Jackaroo: Hm, das ist ja schon ziemlich lange her. Ich muss wohl gedacht haben es werden verschachtelte XML-Strukturen aus Pfadangaben erstellt. Das hätte ich rekursiv gemacht. Aber so bringt das bei `write_asset()` keinen Vorteil. Im Gegenteil.

*Klassen*attribute sollte man nur sehr sparsam einsetzen. Was Du meinst sind Attribute auf den Exemplaren (a.k.a. „Instanzattribute”). Da hängt es davon ab, ob der Wert den man daran bindet zum Zustand des Objekts gehört, oder nur lokal in einer Methode benötigt wird. Ich denke Du bindest ein bisschen viel an das Objekt. Zum Beispiel müsste die XML-Datei nicht die ganze Zeit offen sein. Es würde genügen, wenn man die ganz zum Schluss nur lokal in einer Methode öffnet, beschreibt, und wieder schliesst.

Ansonsten sieht mir das alles ein wenig kompliziert aus. Keine Ahnung ob das so sein muss. Die Werte aus der CSV-Datei scheinen ja selber noch einmal eine recht aufwendige Struktur zu besitzen.
Jackaroo
User
Beiträge: 13
Registriert: Mittwoch 1. Dezember 2010, 13:28

@BlackJack:

Doch, doch, es werden verschachtelte XML-Strukturen aus Pfadangaben (tag/tag/tag/tag) erstellt. Sieht man das nicht? ( :oops: ) Aktuell ist der erzeugte XML-Baum maximal 4 Ebenen tief. Also nur ein bisschen verschachtelt.

Ich finde, der rekursive Aufruf von write_asset() ist schwerer zu verstehen als eine Schleife, allerdings kommt man ohne eine Einrückungsebene aus. Dann sieht es etwas einfacher aus.

Das mit dem Offenhalten der XML-Datei habe ich sofort geändert. Ist ja einleuchtend.

Für die Instanzattribute habe ich mir mal folgende Erklärung zurechtgelegt: Wenn ein Attribut nur von zwei Methoden benötigt wird, dann kann ich es auch als Parameter hin- und herreichen. Darüberhinaus wird es unübersichtlich und ein Instanzattribut ist einfacher in der Handhabung. Ist das eine vertretbare Sichtweise?

Kopfzerbrechen bereitet mir, dass es immer noch so kompliziert aussieht. Ich habe nun nochmal eine Version gemacht, die ohne den "look ahead" auskommt und nochmals 10% kürzer ist. Aber ob es nun weniger kompliziert ist, weiss ich nicht.
BlackJack

@Jackaroo: Ich sehe nicht wie aus einem Pfad eine verschachtelte Struktur gemacht wird, wie ich das wohl ursprünglich dachte. Ich dachte da wäre eine rekusrive Struktur, womit ein rekursiver Aufruf auch Sinn gemacht hätte. Dass das nicht total flach ist, war mir schon klar.

Das mit den Instanzattributen würde ich nicht nur an der Anzahl von Methoden fest machen, die einen Wert benötigen, sondern auch ob es semantisch für ein Objekt von einem bestimmten Typ Sinn macht, dass ein Attribut dauerhaft zu seinem Zustand gehört.

Das mit der Komplexität kannst Du nur selber einschätzen, es sei denn jemand macht sich die Mühe den bisher gezeigten Quelltext zu „reverse egineer”en, um heraus zu finden was der eigentlich genau machen soll. :-)
Antworten