Seite 1 von 2

lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:18
von burli
Ich bastle gerade an einem Konverter, der XML Files in eine SQL Datenbank packen soll. Dabei gibt es ein paar Unstimmigkeiten und leere Felder in den Dateien, die ich abfangen muss. Derzeit löst ich das alles mit einem try Block. Das sieht dann so aus und wiederholt sich

Code: Alles auswählen

    for module in modules:
        attributes = module.attrib
        try:
            text=attributes["text"].decode("utf-8")
        except Exception, ex:
            text="".decode("utf-8")
        try:
            implements=attributes["implements"].decode("utf-8")
        except Exception, ex:
            implements="".decode("utf-8")
        try:
            name=attributes["name"].decode("utf-8")
        except Exception, ex:
            name="".decode("utf-8")
            
        # Create Module
        Module = model.modules_module(implements=implements, name=name, text=text)
        # Append Module to Device
        Device.module.append(Module)
Die komplette Datei inklusive SQLite Datenbank, Elixir Model und einem kleinen Testprogramm findet ihr hier

Ich weiß, dass man das deutlich besser programmieren kann. Ich mache sowas erstmal straigt forward. Jetzt kommt die Optimierung und Pythonifizierung. Der gesamte Vorgang dauert auch eine Weile. Ich weiß allerdings nicht, ob es an den vielen try Blöcken liegt oder daran, dass die XML Dateien insgesamt über 56MB groß sind. Die Dateien darf man im übrigen nicht weitergeben. Oder zumindest nicht veröffentlichen. Ich habe beim "Hersteller" angefragt und der hat die Bitte abgelehnt, aber in veränderter Form wäre es erlaubt. Deshalb der Aufwand mit dem Konverter (außerdem ist die Datenbank bedeutend kleiner)

Habt ihr ein paar Verbesserungsvorschläge für mich? Ich weiß, ohne die XML Files ist es blöd. Gerhard könnte die kennen (sind die Partdescriptionfiles vom AVR Studio)

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:41
von DasIch
Nimm ein dict statt fuer alles Namen zu nutzen und fang etwas konkretes ab als Exception, man kann da durchaus auch mehrere Typen angeben, dafuer muss man nicht hoeher in die Hierarchie gehen.

Bei dem ``"".decode("utf-8")`` wuerde es auch ``u""`` tun wobei ich mich wirklich Frage ob du da wirklich kein unicode von lxml bekommst. Wundert mich doch schon etwas...

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:49
von burli
DasIch hat geschrieben:Nimm ein dict statt fuer alles Namen zu nutzen und fang etwas konkretes ab als Exception, man kann da durchaus auch mehrere Typen angeben, dafuer muss man nicht hoeher in die Hierarchie gehen.
Wie meinst du das mit dem dict?
DasIch hat geschrieben: Bei dem ``"".decode("utf-8")`` wuerde es auch ``u""`` tun wobei ich mich wirklich Frage ob du da wirklich kein unicode von lxml bekommst. Wundert mich doch schon etwas...
Ohne die Umwandlung bekomme ich auf jeden Fall Gemecker (eine Warnung) und ein type() liefert als Typ <str>

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:52
von burli
Als Ergänzung: die try Blocks waren deshalb nötig, weil die meisten Attribute optional sind. Wenn das Attribut fehlt produziert der Versuch, den Wert zu lesen, einen Fehler.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:54
von Xynon1
burli hat geschrieben:Ohne die Umwandlung bekomme ich auf jeden Fall Gemecker (eine Warnung) und ein type() liefert als Typ <str>
macht ja das u

Code: Alles auswählen

>>> "ö".decode("utf-8")
u'\xf6'
>>> type(u'\xf6')
<type 'unicode'>

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 17:57
von DasIch

Code: Alles auswählen

    for module in modules:
        attributes = module.attrib
        # variante mit dict
        result = {}
        for key in ('text', 'implements', 'name'):
            try:
                result[key] = attributes.get(key, '').decode('utf-8')
            except UnicodeDecodeError:
                result[key] = u''
            text=attributes["text"].decode("utf-8")  
        # variante ohne dict falls es wirklich nicht notwendig ist
        result = []
        for key in ('text', 'implements', 'name'):
            try:
                result.append(attributes.get(key).decode('utf-8')
            except UnicodeDecodeError:
                result.append(u'')
        text, implements, name = result
Eventuell macht es Sinn soetwas auch in eine eigene Funktion auszulagern.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 18:07
von burli
Achso, ja. Sowas ähnliches hab ich auch überlegt. Das Problem ist, dass ich manche Werte in Integer umwandeln will. Leider gibt es da ein ziemliches Durcheinander. Manche Werte stehen dezimal drin, manche als HEX, wahlweise mit $ oder 0x als Prefix. Teilweise steht auch NA, N/A oder NaN drin. Deshalb möchte ich das alles einheitlich als Integer in der Datenbank speichern. Deshalb bringt das mit der Schleife nichts

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 18:16
von Hyperion
Kann es sein, dass lxml bei Zeicheketten im ASCII-Wertenereich Str-Objekte liefert und ansonsten Unicode? Iirc war mir so... dann wäre ja eine Umwandlung mittles unicode() ja problemlos möglich.

Edit: Ist wohl so:

Code: Alles auswählen

In [13]: xml = u"""<test char="blöd" normal="ascii">Hallöle</test>"""

In [14]: root = etree.fromstring(xml)

In [15]: root.attrib["char"]
Out[15]: u'bl\x94d'

In [16]: root.attrib["normal"]
Out[16]: 'ascii'

In [17]: type(root.attrib["char"])
Out[17]: <type 'unicode'>

In [18]: type(root.attrib["normal"])
Out[18]: <type 'str'>

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 18:28
von DasIch
Hyperion hat geschrieben:Kann es sein, dass lxml bei Zeicheketten im ASCII-Wertenereich Str-Objekte liefert und ansonsten Unicode?
Macht natuerlich zur Optimierung Sinn.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 18:41
von burli
Das würde natürlich einiges erklären.

Aber wie könnte ich mein Programm noch optimieren? Ich habe auch überlegt, einfach eine Liste der vorhandenen Attribute zu holen. Dann brauche ich den try Block nicht. Im Elixir Model könnte ich einfach Default Werte vorgeben für Felder, die dann fehlen.

Allerdings hab ich dann das Problem das ich nicht weiß, wann ich es mit Zahlen und wann mit Strings zu tun habe. Das einzige, was mir einfallen würde, ist eine Liste mit Attributen die Strings enthalten und eine Liste mit den Zahlen und dann einfach vergleichen.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 19:19
von Hyperion
Wieso machst Du es nicht einfach so:

Code: Alles auswählen

for name, raw_value in root.attrib.iteritems():
    try:
        value = int(raw_value)
    except ValueError:
        value = unicode(raw_value)
    print name, value, type(value)
Wobei ich Dein Problem nicht ganz kapiere: Wenn Du doch eh auf ein ORM setzt, hast Du doch ein Datenmodell vorgegeben. D.h. Du musst doch wissen, welches Attribut aus dem XML mit welchem Attribut Deines Modells übereinstimmt? Insofern müßtest Du doch entscheiden können, wann Du ein Attributwert hast, welcher in ein Integer-Wert umgewandelt werden kann.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 19:24
von burli
Nein, das funktioniert wie gesagt nicht. Die Dateien sind richtig inkonsistent. Ein paar Probleme habe ich in meinem Forum schon beschrieben, zum Beispiel dieses oder dieses

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 19:40
von Hyperion
Das sind aber doch keine Attribute, sondern Textknoten!

Aber auch da sehe ich doch kein (großes) Problem. Genau die Umwandlung aolcher Typen ist doch Aufgabe eines Parsers (bzw. Konverters)! Insofern musst Du eben eine Funktion basteln, die je nach Typ die entsprechende Umwandlung erledigt.

Hier einmal für das "NaN"-Problem:

Code: Alles auswählen

In [36]: xml_data = u"""
   ....: <dummy>
   ....: <EXT_SRAM>
   ....:       <SIZE>0</SIZE>
   ....:       <START_ADDR>NA</START_ADDR>
   ....: </EXT_SRAM>
   ....: <EXT_SRAM>
   ....:     <SIZE>NaN</SIZE>
   ....:     <START_ADDR>NaN</START_ADDR>
   ....: </EXT_SRAM>
   ....: <EXT_SRAM>
   ....:       <SIZE>0</SIZE>
   ....:       <START_ADDR>N/A</START_ADDR>
   ....: </EXT_SRAM>
   ....: </dummy>
   ....: """

In [37]: root = etree.fromstring(xml_data)

In [40]: for node in root.iterfind(".//SIZE"):
   ....:     node.text
   ....:
   ....:
Out[40]: '0'
Out[40]: 'NaN'
Out[40]: '0'

In [41]: for node in root.iterfind(".//SIZE"):
   ....:     try:
   ....:         value = int(node.text)
   ....:     except ValueError:
   ....:         value = 0
   ....:     print value
   ....:
   ....:
0
0
0
Für die Startadresse ginge so etwas:

Code: Alles auswählen

In [44]: START_ADDR_SPECIAL_TOKEN = ["NA", "NaN", "N/A"]

In [46]: for node in root.iterfind(".//START_ADDR"):
   ....:     if node.text in START_ADDR_SPECIAL_TOKEN:
   ....:         value = u"definierter Wert"
   ....:     else:
   ....:         value = unicode(node.text)
   ....:     print value
   ....:
   ....:
definierter Wert
definierter Wert
definierter Wert
Hier kannst Du also den drei Varianten einen definierten Wert zuweisen.

Da Du ja weißt, welche Information (bzw. Typ) in Deinem Ziel-Modell stehen kann, sollte das doch alles kein Problem sein!

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 19:48
von burli
Die XML Dateien bestehen aus zwei Teilen. Der alte Teil sind nur einfache XML Tags, im zweiten Teil ist alles mit Attributen gemacht. Teilweise sind die Daten doppelt vorhanden, teilweise nur im ersten _oder_ zweiten Teil. Ich möchte das zusammenfassen.

Für die Konvertierung der Werte hab ich mir schon eine extra Funktion geschrieben. Das ist jetzt weniger das Problem. Das Problem ist, dass ich nicht wie von DasIch vorgeschlagen vorgehen kann, weil Strings und Zahlen gemischt vorkommen.

Ich weiß das es schwierig ist, wenn man die XML Files nicht hat. Aber ich habe eine Idee, wie es funktionieren könnte. Ein paar Anregungen hab ich ja bekommen.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 20:18
von Hyperion
burli hat geschrieben:Die XML Dateien bestehen aus zwei Teilen. Der alte Teil sind nur einfache XML Tags, im zweiten Teil ist alles mit Attributen gemacht. Teilweise sind die Daten doppelt vorhanden, teilweise nur im ersten _oder_ zweiten Teil. Ich möchte das zusammenfassen.
Naja Du hast Doch Zielmodelle (Klassen in Bezug Elixir) und für gewisse Objekte stehen eben Infos in zwei Dateien. Also musst Du die Attribute eines Objektes eben "auffüllen" - das ist aber doch eigentlich kein Problem.
burli hat geschrieben: Das Problem ist, dass ich nicht wie von DasIch vorgeschlagen vorgehen kann, weil Strings und Zahlen gemischt vorkommen.
Das kapiere ich jetzt nicht! Ich habe ja ein Beispiel genannt, welches Textwerte in Integers "übersetzt" - wenn eben statt einer "0" dort ein String steht, so muss der doch einem festen Wert zugeordnet werden können. Diese Zuordnung musst Du eben z.B. in Form von Listen / Dictionaries festlegen. Irgend wie sehe ich da immer noch kein wirkliches Problem. :K

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 20:42
von burli
Wie ich schon sagte, die Zahlen kommen in verschiedenen Formen vor. Einmal dezimal, in zwei HEX Varianten und dann eben diese NA, N/A, NaN Geschichte.

Es kann also 255 drin stehen, $FF, 0xFF, einer der drei Strings oder das Feld ist leer.

Und das nächste Problem ist, dass ich mir nicht sicher sein kann, dass ein bestimmtes Feld in allen Dateien gleich ist. Bei einigen Dateien steht der Wert dezimal drin, in einer anderen Datei als HEX Wert. Ich kann also nicht einfach davon ausgehen, dass es sich um einen String handelt, wenn ein int() nicht funktioniert.

Aber das Problem habe ich eigentlich schon lange gelöst. Es geht mir mehr darum, wie ich das eigentliche Einlesen optimieren kann um auf möglichst viele try Blöcke verzichten zu können. Ich hab die try Blöcke nur deshalb, weil die Attribute nicht immer alle vorhanden sind. Der Zugriff z.B. mit text=attributes["text"] erzeugt aber einen Fehler, wenn das Attribut "text" nicht vorhanden ist.

Aber wie gesagt, ich habe eine Idee und möchte die erstmal versuchen umzusetzen. Ich mache einfach eine Liste mit Attributen, die Strings enthalten und eine Liste, die Zahlen enthalten. Dann mache ich eine Schleife mit "for name, raw_value in root.attrib.iteritems():", aber ich vergleiche den Key dann mit den beiden Listen und entscheide, ob es ein String oder eine Zahl sein müsste

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 20:51
von Hyperion
burli hat geschrieben:Ich hab die try Blöcke nur deshalb, weil die Attribute nicht immer alle vorhanden sind. Der Zugriff z.B. mit text=attributes["text"] erzeugt aber einen Fehler, wenn das Attribut "text" nicht vorhanden ist.
Ach so... na dafür gibt es doch "{}.get()".

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 21:07
von burli
Hyperion hat geschrieben:Ach so... na dafür gibt es doch "{}.get()".
Das führt aber zu einem anderen Problem, welches mich zu einem try oder anderen Maßnahmen zwingt
Traceback (most recent call last):
File "pdfconvert.py", line 83, in <module>
text=attributes.get("text").decode("utf-8")
AttributeError: 'NoneType' object has no attribute 'decode'
Ich will/muss ja eine Umwandlung in Unicode machen, was bei einem None nicht funktioniert. Deshalb sieht die Zeile ja normalerweise so aus

Code: Alles auswählen

        try:
            text=attributes["text"].decode("utf-8")
        except Exception, ex:
            text="".decode("utf-8")
Einen leeren String kann ich umwandeln. Es ändert sich also nichts daran, DASS eine Exception ausgelöst wird, nur der Grund ist ein anderer. Ich könnte natürlich das decode weglassen und hinterher mit if arbeiten, aber das will ich mir ersparen

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 21:11
von DasIch
@burli Du hast meinen Code schon gesehen? Da wir jetzt wissen dass ein UnicodeDecodeError ohnehin nicht auftritt kannst du sogar aufs try..except verzichten.

Re: lxml: Alternative Fehlerbehandlung für try

Verfasst: Samstag 13. November 2010, 21:18
von Hyperion
burli hat geschrieben:
Hyperion hat geschrieben:Ach so... na dafür gibt es doch "{}.get()".
Das führt aber zu einem anderen Problem, welches mich zu einem try oder anderen Maßnahmen zwingt
Traceback (most recent call last):
File "pdfconvert.py", line 83, in <module>
text=attributes.get("text").decode("utf-8")
AttributeError: 'NoneType' object has no attribute 'decode'
Ich will/muss ja eine Umwandlung in Unicode machen, was bei einem None nicht funktioniert. Deshalb sieht die Zeile ja normalerweise so aus
Wieso wandelst Du nicht immer per

Code: Alles auswählen

unicode(value)
? Danach kannst Du Dich doch dann um das Parsen des Inhaltes kümmern (Also prüfen, ob eine Zahl, eine hexadezimale Darstellung usw. vorhanden ist).

Zudem kann man .get() ja einen default-Parameter mitgeben, der ggf. ein leerer String sein könnte.

Aber ich würde die Umwandlung eben einfach immer durchführen, dann bist Du auf der sicheren Seite! (Und performant sollte das auch sein, da laut Doku ein Unicode-Objekt nicht "erneut" in Unicode gewandelt wird)