Insert erst beim zweiten Anlauf erfolgreich

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

Hallo Leute,

ich habe da mal wieder eine Frage. Dieses mal indirekt zu Python sondern eher zu der MySQL-Anbindung.
Ich habe in meinem Testprojekt (nun mein zweites, quasi 'Fortgeschrittenes Projekt') Fußballdaten die ich in meine MySQL-DB einfügen möchte.
Ich habe eine XML mit einer kompletten Saison an Daten.
So, jetzt parse ich diese XML und sammle alle relevanten Daten in meine Klasse.
Dazu u.a. Saison, Liga, Datum, Runde, Paarung mit Teams, Ergebnis usw..

So weit so gut.
Ich befülle also meine Membervariablen die in verschiedene Bereiche gegliedert sind.
Ligaübergreifende, dann Rundendaten, dann Matchdaten.
Es gibt in der Bundesliga ja 34 Spieltage, also Runden. Ich schleife also bis zum Ende 34 Mal durch. Nach jedem Schleifendurchgang werden die betreffende Membervariablen resettet und neu befüllt mit den Daten der nächsten Runde. In jeder Runde gibt es 9 Spielpaarungen die auch wiederrum durchgeschleift werden. Da resette ich eben nur diesen Bereich nach jedem Schleifendurchgang und befülle die mit relevanten Daten.

Sprich: nach jedem Match befülle ich die DB mit den Daten bevor ich die entsprechenden Daten resete und zum nächsten Match schleife.
Dabei werden auch die immer wiederkehrende bzw. gleiche Daten in den Tabellen für die Teams, Liga, Saison, Stadion, usw. befüllt.
Dabei verwende ich ein Insert-Statement welcher sicherstellt das es die Daten nicht gibt bevor sie befüllt werden und falls doch, dann gibt es keine Daten zum befüllen. (Insert into... (Select id from tabelle where foreign_id = 12345 limit 1). Es gibt also keine Fehlermeldung der DB selber das ein Key bereits vorhanden ist oder der Gleichen...

Passt auch alles ganz gut.
Jetzt habe ich aber ein Phänomen:
Beim ersten Durchgang fehlen mir die kompletten Daten der ersten Runde. Alle darauffolgenden Runden sind aber vorhanden.
Lasse ich das Programm gleich nochmals laufen, so sind dann auch die Daten der 1. Runde vorhanden.
Das SQL-Statement habe ich mir auch ausgeben lassen (print cursor.last_executed) und kopiere das Statement und führe es manuell in der DB aus nach dem ersten Druchgang (um sicherzustellen das andere relevante Daten vorhanden sind da diese ja vor den Spielpaarungsdaten befüllt werden falls nicht schon vorhanden).
Da ist kein Synthax-Fehler oder der Gleichen und er führt es ja auch wie gewünscht aus in der MySQL-Workbench.
Auch die Daten des Insert-Statements, die aus andere Tabellen stammen, die eventuell noch nicht vorhanden sein könnten weil sie vor der ersten Runde doch befüllt werden (und eventuell aus Zeitgründen noch nicht verfügbar wären) sind aber ganz normal vorhanden in dem ausgegeben Statement (cursor._last_executed).
Somit kann es auch kein 'Zeitproblem' der DB sein.

Was kann ich tun um dem Fehler auf den Grund zu gehen?
Synthaktisch ist alles ok. Dateninhaltstechnisch auch. Es muss an der DB oder der Verbindung selber liegen.
Es passiert ja nur beim ersten Durchgang wenn auch alle anderen Tabellen VORHER im Programm erstmalig befüllt werden.
Gibt es ein Befehl bei dem ich eventuell die connection resetten kann ohne sie zu schliessen und neu aufzubauen?
Oder meint ihr es liegt an was Anderem?
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: Code sagt mehr als tausend Worte. In Deiner Erklärung sind auch ein paar seltsame Beschreibungen drin, die ich nicht verstehe. Was heißt z.B. ›resetten‹?
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius3

Das mit dem Code ist schwer. Ich müsste die ganze Klasse kopieren und eventuell auch die Parent-Klasse von welcher geerbt wurde.

Aber ich versuche es mit den Schipseln welche ich denke die aussagekräftig sind:

Code: Alles auswählen

from class_xml_parse import xml_parser_class
from class_mysql import MySQL
import xml.etree.ElementTree as ET

class xml_soccer_historic(xml_parser_class):
    def __init__(self, url):
        xml_parser_class.__init__(self, url)
        #allgemein
        self.db                 = MySQL()
        
        #Member gültig für komplette XML (Land, Liga usw.)
        self.country            = ""
        self.league             = ""
        .
        .
        .
        #Member gültig für Spieltag alle Spielpaarungen
        self.weekno             = ""
        
        #Member gültig pro Spielpaarung
        self.match_date         = ""
        self.match_time         = ""
        self.venue              = ""
        self.venue_id           = ""
        .
        .
        #Heimteam
        self.ht
        self.ht_id              = ""
        self.ht_score           = 0
        .
        .
        #Auswärtsteam
        self.at
        self.at_id              = ""
        self.at_score           = 0
        .
        .
        #Aufstellungen
        self.ht_lineups         = []
        self.at_lineups         = []
        #Ereignisse (Tore, Karten, Auswechslungen)
        self.scores             = []
        self.ht_substitution    = []
        self.at_substitution    = []
        self.bookings           = []
        
    def reset_all(self):
        self.reset_country()
        self.reset_tournament()
        self.reset_week()
        self.reset_match()
        self.reset_match_detail()
    
    def reset_country(self):
        self.country            = ""
    
    def reset_tournament(self):
        self.league             = ""
        self.season             = ""
        self.league_stage_id    = ""
        self.league_id          = ""
    
    def reset_week(self):
        self.weekno             = ""
    
    def reset_match(self):
        self.match_date         = ""
        self.match_time         = ""
        .
        .
        .
    #Hier noch meine Funktionen/Methoden die u.a. Attribute der Nodes auslesen, diesu nd jenes aufbereiten usw.
    def getAllNodeAttribs(self, node):
        tmpDict = {}
        for elem in node.attrib.items():
            tmpDict.update({elem[0] : elem[1]})
        return(tmpDict)
    def usw(self):
        .
        .
        
    #Parsen geht los
    def parse(self):
        self.db = MySQL()
        
        if self.xmlcontainer != "":
            root = ET.fromstring(self.xmlcontainer)
            #Hier werden alle Member resettet/gelöscht
            self.reset_all()
            self.country = self.getAllNodeAttribs(root)["country"]
            
            for tournament in root.findall(".//tournament"):
                #Hier werden alle Member betreffend der Liga resettet/gelöscht. Ist eigentlich unnötig weil eine XML = eine Liga darstellt.
                self.reset_tournament()
                
                tournament_attrib = self.getAllNodeAttribs(tournament)
                self.league = tournament_attrib["league"]
                self.season = tournament_attrib["season"]
                .
                .
                
                for week in tournament.findall(".//week"):
                    #Hier werden alle Member der Spielwoche/Runde gelöscht um diese neu zu befüllen mit neuen Werten
                    self.reset_week()
                    
                    week_attrib = self.getAllNodeAttribs(week)
                    self.weekno = week_attrib["number"]
                    
                    for match in week.findall(".//match"):
                        #Hier werden nun die Spielpaarungen und die Details zu jedem Spiel resettet und dann befüllt
                        self.reset_match()
                        self.reset_match_detail()
                        
                        match_attrib = self.getAllNodeAttribs(match)
                        self.match_id = match_attrib["id"]
                        self.match_static_id = match_attrib["static_id"]
                        self.venue = match_attrib["venue"].split("(")[0].strip()
                        self.venue_id = match_attrib["venue_id"]
                        self.match_date = match_attrib["date"]
                        self.match_time = match_attrib["time"]
                        .
                        .
                        .
                        
                        #Nun kommen die Ereignisse 'Tore' in eine Liste mit Dictionaries
                        tmpSubElement = match.find('goals')
                        for item in tmpSubElement.findall(".//goal"):
                            item_attrib = self.getAllNodeAttribs(item)
                            dictTemp = {}
                            temp = item_attrib["player"]
                            .
                            .
                            dictTemp.update({"player": temp})
                            dictTemp.update({"playerid": item_attrib["playerid"]})
                            .
                            .
                            dictTemp.update({"minute": self.sumOTMinute(item_attrib["minute"])})
                            .
                            .
                            self.scores.append(dictTemp)
                            
                        #Nun die Aufstellung der Teams
                        for i in range(2):
                            if i == 0:
                                team = "localteam"
                                isVisitor = "0"
                                dictLU = self.ht_lineups
                            else:
                                team = "visitorteam"
                                isVisitor = "1"
                                dictLU = self.at_lineups
                            
                            tmpSubElement = match.find('lineups').find(team)
                            for item in tmpSubElement.findall(".//player"):
                                item_attrib = self.getAllNodeAttribs(item)
                                dictTemp = {}
                                dictTemp.update({"name": item_attrib["name"]})
                                dictTemp.update({"id": item_attrib["id"]})
                                dictTemp.update({"number": item_attrib["number"]})
                                .
                                .
                                dictLU.append(dictTemp)
                        
                        #Danach noch weitere ähnliche Abschnitte für Auswechslungen und Karten
                        
                        #Hier eine Methode welche die aktuellen Membervariablen auf vorhandensein und auf plausibilität prüft (es kann kein Spieler eine Tor schiessen der nicht aufgestellt ist)
                        if self.checkData():
                            #Prüfung erfolgreich / Daten ok -> Ab in die DB
                            self.insertDB()
    
    def insertDB(self):
        #Land wird in die Tabelle eingefügt. Meine eigene 'id' ist AI und dient fortan als PK
        sql = "insert into country (name) select * from (select %s) AS tmp where not exists (Select id from country where name=%s) limit 1;"
        self.db.sql_insert(sql, (self.country, self.country))
        
        #Saison wird in die Tabelle eingefügt. Meine eigene 'id' ist AI und dient fortan als PK
        sql = "insert into soccer_season (name) select * from (select %s) AS tmp where not exists (Select id from soccer_season where name=%s) limit 1;"
        self.db.sql_insert(sql, (self.season, self.season))
        
        #Stadien
        .
        .
        #Teams
        .
        .
        #Spieler
        .
        .
        #HIER KOMMEN NUN DIE SPIELPAARUNGEN. BEI WEEKNO = 1 wird NICHT BEFÜLLT. DANACH ABER ALLE IM SELBER DURCHGANG PROBLEMLOS. IM zweiten Durchgang wird aber auch WeekNo = 1 inserted!
        tlpValues = ()
        sql = "insert into soccer_matches (foreign_id, ...., MatchDate, MatchTime, HomeTeamID, AwayTeamID, SeasonID, ..., VenueID, HomeTeamScore, AwayTeamScore, HomeTeamHTScore, "
        sql += "AwayTeamHTScore, CountryID, GameWeek, LeagueID, ..) select * from ("
        sql += "select %s as foreign_id, .... , %s as MatchDate, %s as MatchTime, ht.id as HomeTeamID, at.id as AwayTeamID, s.id as SeasonID, ..., ven.id as VenueID, %s as HomeTeamScore, "
        sql += "%s as AwayTeamScore, %s as HomeTeamHTScore, %s AwayTeamHTScore, ...., "
        sql += "c.id as CountryID, %s as GameWeek, l.id as LeagueID from ((((soccer_team ht inner join soccer_team at on at.foreign_id = %s) "
        sql += "inner join soccer_season s on s.name = %s) inner join venue ven on ven.foreign_id = %s) inner join country c on c.name = %s) inner join soccer_league l on l.foreign_id = %s where ht.foreign_id = %s"
        sql += ") AS tmp where not exists (select id from soccer_matches where foreign_id=%s) limit 1;"
        
        tlpValues = (   self.match_id,
                        self.match_static_id,
                        .
                        .
                        .
                    )
                    
        #Natürlich sind die Tuple-Values 1:1 identisch (in Reihe und Glied) mit den %s-Platzhaltern im SQL-Statement
        
        #Jetzt wird das Memberobjekt der MySQL-Instanz aufgerufen mit der Insert-Methode. sql-Statement und das Tuple mit den Werten für das 'injection' (heisst doch so, oder?))
        self.db.sql_insert(sql, tlpValues)
        
#------------------------------------------------------------------------------------------------------------------------------------------
        
    #Die MySQL-Klasse bzw die Methode ist in einer anderen Python-Datei. Ich hänge nur diesen Abschnitt hier kurzerhand mit rein:
    def sql_insert(self, sql, tlpValues):
        try:
            self.cursor.execute(sql, tlpValues)
            self.db.commit()
        except IOError as e:
            self.db.rollback()
            self.log("mySQL-Error ({0}): {1}".format(e.errno, e.strerror))
So, jetzt fühle ich mich ein wenig wie mit heruntergelassenen Hosen. Ich habe noch nie so viel von meinem eigenen Code gepostet um wenigstens den Anschein zu erhalten ein wenig Ahnung vom programmieren zu haben. Jetzt seht ihr alle das dem nicht so ist...
Aber egal, bin ja anonym...;)

In der Aufrufenden Umgebung wird lediglich der Pfad zur XML ermittelt und dann aufgerufen:

Code: Alles auswählen

.
.
.
    for url in listSrc:
    	#pf -> meine eigenen Funktionen-'Bibliothek' mit u.a. printblock-Funktion die mir eingerahmt etwas ausgibt
    	#'url' ist eine Liste mit Dictionaries mit u.a. einem url-Key dessen Wert mich hier interessiert und ich durchschleife
        pf.printblock("Processing: %s" % (url["url"]))
        xmlObj = xml_soccer_historic(url["url"])
        xmlObj.getXML()
        xmlObj.parse()
So, in der o.g. Klasse funktioniert ALLES Einwandfrei. Keien Synthax-Fehler, keine logischen Fehler (in der DB landet alles dort wo es sein soll und die refentielle Integrität ist komplett gewahrt. DB ist in einer geschätzten 2,5-ten Normalform), kein nichts.
NUR das im ersten Durchlauf in erster Schleife alle Daten bis auf die der Spielpaarungen (Matches) in der DB landen. Gleich in der nächsten Schleife, also Runde 2, ist alles ok. In einem neuen Durchlauf landen dann auch die Daten der 1. Runde in der DB.
Also 'NUR' diese blöde eine Stelle....

P.S.bitte um ein wenig Nachsicht. Programmiere python erst seit Anfang letztes Jahr und davon 8 Monate komplette Abstinenz von Python..
Zuletzt geändert von Anonymous am Mittwoch 1. Februar 2017, 00:03, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

Ach ja, um es nochmals zu verdeutlichen wollte ich noch unbedingt folgendes nachreichen:

In der Methode 'sql_insert' habe ich nun eine Abfrage eingebaut die nur dann greift wenn das tlpValue mehr als 14 Einträge hat (also bei nurbisher lediglich wenn ich Spielpaarungen inserte) und das 16. Element (Runde / Spielwoche) = 1 (1. Runde) beinhaltet.
Dann soll er mir das gesamte Tupel ausgeben und den Return-Wert der 'execute'-Methode ausgeben.
Sieht dann so aus:

Code: Alles auswählen

    def sql_insert(self, sql, tlpValues):
        try:
            if len(tlpValues) > 14:
                if tlpValues[15] == "1" in tlpValues: 
                    print tlpValues
                    print self.cursor.execute(sql, tlpValues)
                    self.db.commit()
            else:
                self.cursor.execute(sql, tlpValues)
                self.db.commit()
        except IOError as e:
            self.db.rollback()
            self.log("mySQL-Error ({0}): {1}".format(e.errno, e.strerror))
Beim ersten Durchgang bekomme ich die Werte angezeigt und bei dem Return-Wert der Execute-Methode eine 0.
Beim nochmaligen ausführen genau das Gleiche nur mit dem Wert 1. Das heisst dann daß er die Werte eingefügt hat.
Das Tuple ist aber 100% identisch!

Das Gleiche habe ich auch nochmals statt dem Tuple mit dem 'cursor._last_executed' ausgegeben.

Code: Alles auswählen

    def sql_insert(self, sql, tlpValues):
        try:
            if len(tlpValues) > 14:
                if tlpValues[15] == "1" in tlpValues: 
                    print self.cursor.execute(sql, tlpValues)
                    print self.cursor._last_executed
                    self.db.commit()
            else:
                self.cursor.execute(sql, tlpValues)
                self.db.commit()
        except IOError as e:
            self.db.rollback()
            self.log("mySQL-Error ({0}): {1}".format(e.errno, e.strerror))
Das Gleiche Ergebnis. SQL-Statement ist auch identisch! Ich kann es kopieren und manuell in der MySQL-Workbench ausführen und dann landen die Daten auch in der DB. Also wie wenn ich es zum zweiten Mal ausführe. Das Bedeutet aber auch das die Sythax i.O. ist.
Zuletzt geändert von Anonymous am Mittwoch 1. Februar 2017, 00:08, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

AAAHHH. Ich hab es gefunden!
Es ist kein sythaktischer Fehler, es ist doch einer der referentiellen Integrität!
Ich habe vergessen das Gastteam im ersten Durchgang auch mit einzufügen! Natürlich kann mein Isnert-Statement dann kein Kex des Gastteams einfügen. Im zweiten Durchgang hat er dann alle Teams abgedeckt (Alle spielen sie ja abwechselnd zu Hause und Auswärts) und ab da hat er dann auch wirklich alle Teams.....
Oh man, sorry. Also ich nehme alles zurück und behaupte das Gegenteil!
Und ja, ich kann nicht programmieren.... :(


Vielen Dank für eure zuhören. Das hilft auch sehr sehr oft und wenn man seine Fehler hier rekonstruieren versucht dann kommt man irgendwann mal auch eventuell selber auf die Lösung bzw. das Problem!


Riesen SORRY!
BlackJack

Das sieht nach einer Klasse pro Datei aus und ”Klasse” ist eigentlich ein Modul mit vielen ”Funktionen” und viel ``global`` in syntaktisch in eine Klasse verschoben um das Schlüsselwort ``global`` nicht schreiben zu müssen, aber letztendlich mit dem gleichen Effekt von ganz viel undurchsichtig geteiltem Zustand.

Zu viele Attribute, zu wenige Arguemente, zu wenige ``return``\s wie es aussieht. Und diese `reset_*()`-Methoden sind furchtbar. Das sollten alles eigene Objekte sein, und wenn es nur `collections.namedtuple` sind die erstellt und zurück gegeben werden, statt als Unmengen ”unabhängiger” Attribute auf einer Gottklasse zu existieren. Ich würde einen *Parser* auch nichts in eine Datenbank eintragen lassen. Das gehört nicht zum Aufgabenbereich.

`getAllNodeAttribs()` verstehe ich nicht. Dir ist klar dass das letztlich einfach nur ein ``return dict(node.attrib)`` ist? Und warum kopierst Du das *überhaupt* in ein anderes Wörterbuch? Erscheint mir jetzt auf den ersten Blick nicht wirklich sinnvoll.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack
Das sieht nach einer Klasse pro Datei aus und ”Klasse” ist eigentlich ein Modul mit vielen ”Funktionen” und viel ``global`` in syntaktisch in eine Klasse verschoben um das Schlüsselwort ``global`` nicht schreiben zu müssen, aber letztendlich mit dem gleichen Effekt von ganz viel undurchsichtig geteiltem Zustand.
Es ist eine von mir erstellte Klasse welche vom Parser vererbt wurde. Der Parser holt die XML-Daten abhängig ob sie als URL oder Datei vorliegt.
Diese Basis-Klasse vererbe ich an die 'soccer_historic'-Klasse welche die an diesen XML-Baum angepasste Daten zugreift / parst.
Die Daten werden dann, nach einem plausibilitätscheck in das Instanzierte MYSQL-Objekt gereicht welcher sich um das einfügen in die DB kümmert.

Die aufrufende Umgebung schleift dann durch alle Dateien / URLS und erledigt den Job.

So, jetzt verstehe ich nicht was ich da anders machen könnte. Bin aber natürlich ganz Ohr um es 'richtig' zu machen.
Ich dachte aber immer eine Klasse soll die Realität irgendwie abbilden. Und diesbezüglich macht sie das hier ja auch irgendwie.
Es gibt Ligen, Saisons, Runden, Teams, Paarungen, Ergebnisse usw.. Und jedes dieser Attribute bekommt eine Membervariable / Memberliste.
Wie soll ich das verfeinern?
Zu viele Attribute, zu wenige Arguemente, zu wenige ``return``\s wie es aussieht. Und diese `reset_*()`-Methoden sind furchtbar. Das sollten alles eigene Objekte sein, und wenn es nur `collections.namedtuple` sind die erstellt und zurück gegeben werden, statt als Unmengen ”unabhängiger” Attribute auf einer Gottklasse zu existieren. Ich würde einen *Parser* auch nichts in eine Datenbank eintragen lassen. Das gehört nicht zum Aufgabenbereich.
Ich glaube jetzt verstehe ich so langsam. Meinst du jedes dieser von mir hier als Attribute gehaltenen Werte sollte eine eigene Klasse bilden und aus der Aufrufenden Umgebund gesteuert werden? Oder diese Klassen in eine Parentklasse zusammenführen und diese dann die Child-Klassen verwalten lassen?

Das mit dem Reset ist eben daraus gewachsen das ich die Klasse über die ganze 'Datei' drübergesülpt habe. Das sich in jedem Schleifendurchgang Bereiche ändern ist DANN hier nicht zu vermeiden und ich muss sicherstellen das die vorherigen Daten aus en Membern verschwinden.
Aber ok, dein vorgeschlagener Ansatz würde das obsolent machen. Dann lösche ich das entsprechende Objekt und kreire für die nächste Gegebenheit ein neues (Spielpaarung z.B.). Habe ich das soweit richtig verstanden?

Wegen dem Parser und seines Aufgabenbereiches:
Ja wie soll ich die Daten dann in die DB bekommen? Als Return-Wert in der aufrufenden Umgebung und dort dann die SQL-Klasse machen lassen?
`getAllNodeAttribs()` verstehe ich nicht. Dir ist klar dass das letztlich einfach nur ein ``return dict(node.attrib)`` ist? Und warum kopierst Du das *überhaupt* in ein anderes Wörterbuch? Erscheint mir jetzt auf den ersten Blick nicht wirklich sinnvoll.
Sorry, jetzt steige ich aus.
Ich habe eine Methode erstellt welche mir ein Dict mit allen Attributen zurückliefert damit ich über den Key darauf zugreifen kann UND um durch das komplette Dict iterieren zu können.
Wenn ich mir jetzt, nachdem ich den letzten Satz geschrieben habe, deine Zeile nochmals anschaue, dann vermute ich das ich mir tatsächlich umsonst die Mühe gemacht habe. Dein Code liefert ja in einem kurzen Einzeiler ja auch das Gleiche zurück...
Danke für den Hinweis. Werde ich anpassen und mir, hoffentlich, für die Zukunft merken. Manchmal denkt man dann doch zu kompliziert. Aber da fehlt mir wohl die 'Genialität' mit einfachen Mitteln das Ziel zu erreichen.

Aber spätestens mit dem 'anderen Wörterbuch' steige ich wirklich aus. Was meinst du mit 'Wörterbuch'?
BlackJack

@kaineanung: Ein Ansatzpunkt ist wahrscheinlich die Attribute die zusammen „resettet“ werden zu Klassen zusammen zu fassen. Und ob das jetzt Attribute auf einer Parser-Klasse werden das müsste man erst einmal sehen. Wahrscheinlich braucht man da nicht mal eine Klasse für. Ich würde erst einmal mit Funktionen anfangen und dann schauen ob eine Klasse für den Parser überhaupt Sinn macht. Die einzelnen Teile des Parsers könnte man auch auf als `classmethod`\s auf die Klassen verteilen welche den einzelnen Teilen des XML-Dokuments entsprechen.

Objekte löschen ist in dem Sinne in Python nicht möglich, das passiert automatisch wenn ein Objekt nicht mehr erreichbar ist. Aber die Objekte braucht man ja noch um sie in die Datenbank eintragen zu können, also muss man die alle aufheben. Parsen überführt eigentlich nur eine Repräsentation (hier XML) in eine andere (hier Objektbaum).

Um den Objektbaum dann in eine Datenbank weg zu speichern schreibt man separaten, vom parsen unabhängigen Code.

Eine übliche Aufteilung bei Code ist Eingabe, Verarbeitung, und Ausgabe zu trennen. Eingabe ist hier das verarbeiten der XML-Datei in eine interne Darstellung. Verarbeitung fällt hier entweder flach oder eher gering aus, und Ausgabe wäre die interne Darstellung in eine Datenbank zu schreiben.

Eine Motivation für so eine Trennung ist das Testen und die Fehlersuche. Wenn man das alles zusammen kodiert, dann muss man zum Testen oder bei der Fehlersuche im Eingabeteil schon eine SQL-Datenbank zur Verfügung haben die beim Testen der Eingabe auch noch schreibend verändert wird. Das ist doch unglaublich unpraktisch.

``node.attrib`` also das `attrib`-Attribut von einem `Element`-Objekt ist laut Dokumentation ja bereits ein Wörterbuch (englisch: „dictionary“). Mein Einzeiler kopiert das in ein neu erstelltes Wörterbuch, was aber nur notwendig ist, wenn man vorhat es zu verändern und sicher sein möchte, dass sich dadurch das XML-Dokument im Speicher nicht verändert. Wenn man nur lesend darauf zugreift, kann man sich die Kopie sparen und direkt auf `attrib` zugreifen.

Was mir noch aufgefallen ist: Der Missbrauch der `dict.update()`-Methode. Die ist dazu da um ein Wörterbuch mit dem Inhalt eines anderen zu aktualisieren, nicht um *einem* Schlüssel einen neuen Wert zuzuweisen. Also entweder hat man da als Argument ein Wörterbuch das dynamisch erstellt wurde, oder eines das mehr als ein Schlüssel/Wert-Paar enthält. Sonst macht diese Methode keinen Sinn weil sie zu umständlich ist und ein zusätzliches, temporäres Wörterbuch mit der Länge 1 erstellt werden muss.
Antworten