Muss man xmlparser schließen?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Servus Zusammen

ich hab mir hier mal einen xml editor gebastelt. nun hätt ich nur noch die frage, muss ich den auch irgendwie schließen, oder reicht ein del der klasse am ende des scripts völlig aus?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xml.etree.ElementTree


class XmlEditor:
    def __init__(self, filename):
        self.filename = filename
        self.tree = xml.etree.ElementTree.parse(self.filename)

    def edit_bool(self, parent, child=None, value=None):
        if not value: return
        
        root = self.tree.getroot()
        parenttoedit = root.find('.//%s' % parent)
        if child:
            childtoedit = parenttoedit.find('.//%s' % child)
        else:
            childtoedit = parenttoedit
        childtoedit.clear()
        xml.etree.ElementTree.SubElement(childtoedit, 'true' if value else 'false')

    def edit_string(self, parent, child, value=None):
        if not value: return

        root = self.tree.getroot()
        parenttoedit = root.find('.//%s' % parent)
        childtoedit = parenttoedit.find('.//%s' % child)
        childtoedit.text = value

    def save_file(self, newfilename=None):
        if not newfilename: newfilename = self.filename
        self.tree.write(newfilename, encoding='UTF-8', xml_declaration="version=1.0")


helgeinterfacexml = XmlEditor('helgeinterface_new.xml')

helgeinterfacexml.edit_bool(parent='ActivatePiInTapeMode', value=True)
helgeinterfacexml.edit_bool(parent='ComfortBlink', child='active', value=True)

helgeinterfacexml.edit_string(parent='ComfortBlink', child='BlinkinTime', value='2600')

helgeinterfacexml.save_file()
del helgeinterfacexml
empty Sig
BlackJack

@harryberlin: Was willst Du denn da schliessen? Und lass bitte das ``del`` weg, das macht keinen Sinn. Das ist kein Mittel zur Speicherverwaltung! Damit wird der *Name* gelöscht, nicht das Objekt. Das ist nur eine *mögliche* Folgeerscheinung, aber nicht garantiert. Wenn Du anfängst ``del`` zu verwenden weil Du Dir um Speicher gedanken machst, dann machst Du etwas falsch.
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

das war im grunde meine frage, ob das mit dem del sinnvoll ist.
muss ich die instanzierte klasse nicht wieder auflösen?
oder muss ich sie helgeinterfacexml = None setzen?

wenn nicht, wie löse ich das objekt denn richtig auf?
empty Sig
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Warum denkst du denn, dass das Objekt "aufgeloest" werden sollte?
Da du von "schliessen" redest: In dem Code existiert kein laengerfristig existierendes Dateiobjekt und schon gar keines, ueber das du Kontrolle haettest. Demnach auch keine Datei, die du schliessen muesstest.
BlackJack

@harryberlin: Du löst das Objekt gar nicht auf (und die Klasse schon mal gar nicht (die ist ja auch ein Objekt)). Weder mit ``del`` noch auf `None` setzen. Was soll das bringen? Beides hat erst einmal nur Einfluss auf den Namen. ``del`` löscht ihn (aber nicht das Objekt) und neu binden, nun ja bindet ihn halt an ein anderes Objekt, was erst einmal auch keinen Einfluss auf ein Objekt hat was vorher vielleicht an diesen Namen gebunden war. Du kannst Objekte nicht explizit aus dem Speicher löschen. Diese Möglichkeit bietet Dir Python nicht. Mach Dir da einfach keine Gedanken drüber. Wenn Du sauber programmierst, kümmert sich die Laufzeitumgebung schon hinreichend gut um den anfallenden Speichermüll.

Noch ein paar Anmerkungen zum Quelltext:

In Python 2 sollte man immer von `object` erben wenn es sonst keine Basisklasse gibt. Sonst hat man eine ”old style class” mit der nicht alles so wie in der Dokumentation beschrieben funktioniert.

Was soll der ``if not value:``-Test? Der ist in beiden Methoden falsch weil er in der ersten verhindert das man `False` verwenden kann und in der zweiten das man eine leere Zeichenkette verwenden kann. Ich sehe auch nicht wie(so) der Wert optional sein soll. Wenn man keinen Effekt haben möchte, dann sollte man die Methode ganz einfach nicht aufrufen statt das die bei bestimmten Werten einfach nicht das macht was der Name vermuten lässt.

Warum unterscheidet sich die API der beiden `edit_*`-Methoden was `child` angeht? Die haben selbst mit dem Unterschied gemeinsamen Code der zweimal im Programm steht und herausgezogen werden kann.

Ich lande dann ungefähr bei so etwas (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from xml.etree import ElementTree as etree


class XmlEditor(object):

    def __init__(self, filename):
        self.filename = filename
        self.tree = etree.parse(self.filename)

    def _get_element(self, parent, child=None):
        root = self.tree.getroot()
        element = root.find('.//' + parent)
        if child:
            element = element.find('.//' + child)
        return element

    def set_bool(self, parent, value, child=None):
        element = self._get_element(parent, child)
        element.clear()
        etree.SubElement(element, 'true' if value else 'false')

    def set_string(self, parent, value, child=None):
        self._get_element(parent, child).text = value

    def save(self, filename=None):
        self.tree.write(
            filename if filename else self.filename,
            encoding='utf-8',
            xml_declaration=True
        )


def main():
    interface_xml = XmlEditor('helgeinterface_new.xml')

    interface_xml.set_bool('ActivatePiInTapeMode', True)
    interface_xml.set_bool('ComfortBlink', True, 'active')

    interface_xml.set_string('ComfortBlink', '2600', 'BlinkinTime')

    interface_xml.save()


if __name__ == '__main__':
    main()
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

@blackjack
1. zugleich @cofi: ok d.h. wenn ich helgeinterfacexml erneut zuweise, dann regelt es das system selbst, wenn da noch was anderes offen ist. z.B.:

Code: Alles auswählen

interface_xml = XmlEditor('helgeinterface_new.xml')

interface_xml.set_bool('ActivatePiInTapeMode', True)
interface_xml.set_bool('ComfortBlink', True, 'active')

interface_xml.set_string('ComfortBlink', '2600', 'BlinkinTime')

interface_xml.save()
    
interface_xml = XmlEditor('helgeinterface_new.xml')
   
interface_xml.set_bool('ActivatePiInTapeMode', True)
interface_xml.set_bool('ComfortBlink', True, 'active')

interface_xml.set_string('ComfortBlink', '2600', 'BlinkinTime')

interface_xml.save()

ja, ich weiß. könnte man jetzt mit nen loop erledigen.

2.: deine ausführung zum erben verstehe ich nicht, was du damit meinst. kannst du es mit laienhaften worten erklären?

3.: du hast recht, da habe ich nicht aufgepasst, hatte value zuvor als string. also müsste ich

Code: Alles auswählen

if value is None:
nehmen.
Und None habe ich drin, weil ich eben die reihenfolge parent, child, value behalten möchte. weils für mich einfach logischer ist.
Und ja, einen leeren string möchte ich nicht haben.

4.: der einfachheit für mich habe ich diese zeilen in jeder funktion. du magst es vllt. so weniger verstehen, aber ich schon. hast du mir nicht mal gesagt, es gibt keine privaten funktionen. also lass bitte den unterstrich weg :P
empty Sig
BlackJack

@harryberlin: Ad 2.: Laienhaft: Erbe *immer* von einer Basisklasse. Wenn Du keine spezielle hast, dann nimm `object`. Sonst passieren irgendwann komische Dinge. Das hat historische Gründe. Also nicht ``class Parrot:`` sondern ``class Parrot(object):``. Ausser man hat eine Basisklasse, dann nimmt die. Beispiel ``class Parrot(Bird):``. Hier braucht man `object` nicht, denn nach der Faustregel wird irgendwo weiter oben in der Vererbungshierarchie von `object` geerbt.

Ad 3.: Ich finde dieses `parent`/`child` mit optionalem `child` sowieso etwas schräg und sehr speziell. Da würde ich eher in den sauren Apfel beissen und eine Typunterscheidung bei einem einzigen Argument machen, nämlich ob es sich um eine Zeichenkette oder etwas anderes handelt, wobei das andere dann als iterierbares Objekt über (Teil)pfade interpretiert wird.

Und selbst wenn man das so macht wie Du, sollte ein nicht übergebener Wert nicht einfach gar nichts machen, sondern eine Ausnahme auslösen dass dort ein Wert erwartet wird. Weshalb ja dieses ”optionale” an der API irgendwie schräg ist.

Ad 4.: Das hat nichts mit verstehen zu tun sondern damit das man keinen Code und keine Daten im Programm wiederholt, weil das eine potentielle Fehlerquelle ist. Code der mehrfach im Programm steht, muss auch jedes mal beim weiterentwickeln und warten mehrfach berücksichtigt werden. Da passiert es dann ganz schnell das sich Unterschiede einschleichen und sich der Code subtil anders verhält bis hin zu Fehlern die bei einigen Methoden dann auftauchen und bei anderen nicht, und die man dann nicht an *einer* Stelle im Code finden und beheben kann.

Im neuen Code weiter unten ist die Methode jetzt auch etwas länger geworden. Das müsste man sonst in jeder `set_*()`-Methode schreiben die man der Klasse verpassen möchte. Und für `get_*()`-Methoden bräuchte man den Code auch.

`_get_element()` ist ein Implementierungsdetail und keine private Methode im Sinne von Zugriffsschutz. Zum Kennzeichnen von Implementierungsdetails wird ein führender Unterstrich verwendet.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from xml.etree import ElementTree as etree


class XmlEditor(object):

    def __init__(self, filename):
        self.filename = filename
        self.tree = etree.parse(self.filename)

    def _get_element(self, paths):
        if isinstance(paths, basestring):
            paths = [paths]

        element = self.tree.getroot()
        for path in paths:
            element = element.find('.//' + path)

        return element

    def set_bool(self, path_or_paths, value):
        element = self._get_element(path_or_paths)
        element.clear()
        etree.SubElement(element, 'true' if value else 'false')

    def set_string(self, path_or_paths, value):
        self._get_element(path_or_paths).text = value

    def save(self, filename=None):
        self.tree.write(
            filename if filename else self.filename,
            encoding='utf-8',
            xml_declaration=True
        )


def main():
    interface_xml = XmlEditor('helgeinterface_new.xml')

    interface_xml.set_bool('ActivatePiInTapeMode', True)
    interface_xml.set_bool(['ComfortBlink', 'active'], True)

    interface_xml.set_string(['ComfortBlink', 'BlinkinTime'], '2600')

    interface_xml.save()


if __name__ == '__main__':
    main()
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

@blackjack
2.
zum laienhaften Verständnis: ich muss bei "class XmlEditor:" nur in den klammern "object" schreiben -> "class XmlEditor(object):"
Die Erklärung dazu ist, weil's einfach besser ist.

3.
ja, da bin ich deiner meinung. nur die xml ist leider so erstellt und ich muss damit umgehen.
du meinst also, ich sollte zumindest zurück geben, warum ich vorzeitig beende?
z.B. ein print 'child is missing'

4.
sorry, du verkomplizierst den code immer mehr.
a) du schreibst namen um, was ich erst mal verstehen muss.
b) du schreibst funktionen um, was ich erst mal verstehen muss.
c) du schreibst argumente um, die ich erst mal verstehen muss.
d) dann kommt noch eine liste dazu, die ich erst mal verstehen muss.
da komme ich als anfänger nicht mehr mit.
am ende wirds wohl auf ein copy/paste hinaus laufen, was eigentlich nicht mein lernziel war.

ich stimme dir natürlich zu, doppelter code bringt fehler bei einer änderung mit sich, wenn man nicht aufpasst.

btw: deine sonderzeichen `, ``, * verwirren mich. gibts da irgendwo ne legende zu?
empty Sig
BlackJack

@harryberlin: Ad 2.: Genau. :-)

Ad 4.: Und ich dachte ich mache den Code einfacher und allgemeiner. :-)

`edit_*()` war irgendwie ”falsch” oder zumindest ungewöhnlich. Diese Operationen heissen überall anders `set_*()`. Zumal man sich auch gut vorstellen könnte das der Benutzer irgendwann auch mal das Gegenstück dazu brauchen könnte, und spätestens dann wäre eine `get_bool()`/`edit_bool()`-Kombination eine Überraschung für den Leser.

Die Liste war dazu die API allgemeiner zu machen. Statt zwei Teilpfade die `parent` und `child` heissen, kann man nun beliebig viele Teilpfade angeben die nacheinander gesucht werden. Das ist flexibler und man hat weniger Argumente, braucht keines davon optional machen und hat auch eine ”natürliche” Reihenfolge der Argumente.

Meine ”Sonderzeichen” sind reStructuredText (Kurzreferenz), was eigentlich den Anspruch hat, dass es als Auszeichnung in Fliesstext nicht besonders stört, weil man es leicht überlesen kann. Und `*` für *hervorgehoben* kenne ich seit den Urzeiten des Netzes. Das war im Usenet schon üblich und einige News- und Mailclients berücksichtigen das auch beim Darstellen von Textinhalten.

Ich schreibe die Beiträge im Texteditor und da ist mir BBCode zu umständlich zu schreiben. reStructuredText habe ich (neben Markdown) im Kopf/”in den Fingern” und wenn man mal Dokumentation für ein Python-Projekt schreibt, kommt man da auch kaum darum herum wenn man die Standardwerkzeuge verwendet. Und auch für andere Dokumente greife ich gerne auf reStructuredText zurück, weil es einerseits als reine Textdatei gut lesbar ist, man andererseits aber auch leicht HTML und PDF daraus erstellen kann.
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Vielen Dank, jetzt hab ich deine Ausführung verstanden. :)

Musste feststellen, dass ich set_bool auch für richtige werte brauche.
Hab dazu was eingefügt, nämlich set_childnode. Im Grunde das selbe wie set_bool, nur das hier strings erlaubt sind.
Könnte jetzt eigentlich die set_bool löschen und in childnode auf True, False, oder string reagieren.
wobei set_ eigentlich unpassend ist(eher replace_), weil die node(s) mit .clear() gelöscht werden.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from xml.etree import ElementTree as etree


class XmlEditor(object):

    def __init__(self, filename):
        self.filename = filename
        self.tree = etree.parse(self.filename)

    def _get_element(self, paths):
        if isinstance(paths, basestring):
            paths = [paths]

        element = self.tree.getroot()
        for path in paths:
            element = element.find('.//' + path)
        return element

    #def set_bool(self, path_or_paths, value):
    #    element = self._get_element(path_or_paths)
    #    element.clear()
    #    etree.SubElement(element, 'true' if value else 'false')

    def set_childnode(self, path_or_paths, value):
        if value == True:  value = 'true'
        elif value == False: value = 'false'
        #else: use the string

        element = self._get_element(path_or_paths)
        element.clear()
        etree.SubElement(element, value)

    def set_string(self, path_or_paths, value):
        self._get_element(path_or_paths).text = value

    def save(self, filename=None):
        self.tree.write(
            filename if filename else self.filename,
            encoding='utf-8',
            xml_declaration=True
        )


def main():
    interface_xml = XmlEditor('helgeinterface_new.xml')

    interface_xml.set_childnode('ActivatePiInTapeMode', True)
    interface_xml.set_childnode(['ComfortBlink', 'active'], False)

    interface_xml.set_childnode('CarModel', 'e39')

    interface_xml.set_string(['ComfortBlink', 'BlinkinTime'], '2600')

    interface_xml.save()


if __name__ == '__main__':
    main()
empty Sig
Antworten