Geordnete Liste der Attribute einer Klasse

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
pyseidon
User
Beiträge: 19
Registriert: Donnerstag 24. September 2009, 20:25

Moin,

ist es eigentlich möglich eine geordnete Liste der Attribute eine Klasse zu bekommen (so wie sie deklariert wurden)?

Die Elemente eines dict sind ja ungeordnet:

Code: Alles auswählen

class FooBar(object):
    def __init__(self):
        self.foo = ''
        self.bar = ''
        self.yab = ''
        self.aab = ''

print FooBar().__dict__

Code: Alles auswählen

{'aab': '', 'foo': '', 'bar': '', 'yab': ''}
Grüße
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyseidon: auf __dict__ sollte man nicht direkt zugreifen. Was willst Du eigentlich für ein Problem lösen?
pyseidon
User
Beiträge: 19
Registriert: Donnerstag 24. September 2009, 20:25

Das Problem eine Liste zu bekommen, in der die Attribute so aufgelistet sind, wie sie in der jeweiligen Klasse deklariert wurden. :)

Im Prinzip will ich das nach XML umwandeln und dabei die Reihenfolge der Deklaration beibehalten.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Die Attribute gibt es in nicht in der Reihenfolge. Fraglich ist auch in welcher Reihenfolge Attribute gelistet werden sollen, die nicht in ``__init__`` erstellt werden.

Wenn dir diese Reihenfolge wirklich so wichtig ist, duerfte das ``ast`` Modul eine Moeglichkeit sein.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man könnte einen Wrapper mittels `.__setattr__()` schreiben, der das Setzen der Attribute in einem `OrderedDict` oder einer `list` (wenn einen nur die Namen interessieren) protokolliert und anschließend `object.__setattr__()` aufruft. Das hieße, man schreibt eine entsprechende Basisklasse mit einem `._attr_names`-Attribut oder sowas und greift zum Protokollieren darauf zu. Übrigens: Eine überschriebene `.__delattr__()`-Methode als Gegenstück sollte man dann sinnvollerweise auch einbauen.

Die Alternative wäre natürlich, für die Basisklasse direkt `.__dict__` mit einem `OrderedDict` zu überschreiben. Allerdings weiß ich nicht, ob das unangenehme Seiteneffekte hätte.

Auf jeden Fall kommt man nicht drumherum, das Setzen der Attribute irgendwie abzufangen. Denn wenn die Attribute einmal gesetzt wurden und dabei in einem herkömmlichen `dict` gelandet sind, dann kann man die Original-Reihenfolge nicht mehr nachvollziehen. Mit anderen Worten: Ein nachträgliches Modifizieren einer Klasseninstanz oder irgendwelche Kniffe via Introspektion würden nicht zum gewünschten Ziel führen.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Use the source, Luke!

Code: Alles auswählen

#!/usr/bin/env python3.4
# -*- coding: utf-8

import ast
import inspect
import xml.etree.ElementTree as etree
import xml.dom.minidom as minidom


class FooBar:

    def __init__(self):
        self.foo = ''
        self.bar = ''


class XMLBuilder(ast.NodeTransformer):

    def visit_Module(self, node):
        module_node = etree.Element('module')
        body_node = etree.SubElement(module_node, 'body')
        for each in node.body:
            body_node.append(self.visit(each))
        return module_node

    def visit_ClassDef(self, node):
        class_node = etree.Element('class', name=node.name)
        body_node = etree.SubElement(class_node, 'body')
        for each in node.body:
            body_node.append(self.visit(each))
        return class_node

    def visit_FunctionDef(self, node):
        func_node = etree.Element('function', name=node.name)
        args_node = etree.SubElement(func_node, 'arguments')
        for each in node.args.args:
            arg_node = etree.SubElement(args_node, 'argument', name=each.arg)
        body_node = etree.SubElement(func_node, 'body')
        for each in node.body:
            body_node.append(self.visit(each))
        return func_node

    def visit_Assign(self, node):
        assignment_node = etree.Element('assignment')
        targets_node = etree.SubElement(assignment_node, 'targets')
        value_node = etree.SubElement(assignment_node, 'value')
        for each in node.targets:
            targets_node.append(self.visit(each))
        value_node.append(self.visit(node.value))
        return assignment_node

    def visit_Attribute(self, node):
        return etree.Element(
            'attribute', value=node.value.id, attr=node.attr)

    def visit_Str(self, node):
        return etree.Element('string', value=node.s)


def prettify(elem):
    """Return a pretty-printed XML string for the Element."""
    rough_string = etree.tostring(elem, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent='  ')


def main():
    tree = ast.parse(inspect.getsource(FooBar))
    xml_builder = XMLBuilder()
    print(prettify(xml_builder.visit(tree)))


if __name__ == '__main__':
    main()
Ergebnis:

Code: Alles auswählen

<?xml version="1.0" ?>
<module>
  <body>
    <class name="FooBar">
      <body>
        <function name="__init__">
          <arguments>
            <argument name="self"/>
          </arguments>
          <body>
            <assignment>
              <targets>
                <attribute attr="foo" value="self"/>
              </targets>
              <value>
                <string value=""/>
              </value>
            </assignment>
            <assignment>
              <targets>
                <attribute attr="bar" value="self"/>
              </targets>
              <value>
                <string value=""/>
              </value>
            </assignment>
          </body>
        </function>
      </body>
    </class>
  </body>
</module>
Müsste man halt weiter ausbauen und an die eigenen Bedürfnisse anpassen.
Lesestoff dazu:
https://docs.python.org/3/library/inspe ... ource-code
https://docs.python.org/3/library/xml.e ... ttree.html
https://docs.python.org/3/library/ast.html
http://greentreesnakes.readthedocs.org/en/latest/
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyseidon: willst Du XML nur als Serialisierungsformat wie Pickle oder JSON verwenden? Dann ist die Reihenfolge ja egal, weil nach dem Einlesen alles wieder in unsortierten Wörterbüchern landet. Oder hast Du eine XML-Definition, die die Reihenfolge vorschreibt. Dann mußt Du die XML-Struktur explizit definieren, weil Tag-Namen nicht den Python-Namensgebung entsprechen, weil manche Attribute vielleicht XML-Attribute und andere Subelemente werden, weil Du Hilfsattribute hast, die nicht mitgespeichert werden sollen/dürfen, weil es Datentypvorgaben gibt, die Du prüfen mußt. Listen kann man ja auf verschiedene Arten in XML abbilden, und müßte dann verschiedene Listen-Klassen in Python verwenden, womit dann Deine Programmkomplexität enorm steigt, nur um das richtige XML zu erzeugen.

Ich habe mal einen XSD-Converter geschrieben, der die Definitionen in Wrapperklassen umschreibt, und bin daran gescheitert, dass XSD einfach viel zu kompliziert ist. Dann habe ich die Object-XML-Wrapperklassen von Hand geschrieben, um daraus XSD- bzw. XML-Dateien schreiben zu können, ähnlich SQLAlchemy. Das Ergebnis war nicht befriedigend, da der Strukturoverhead für meinen Geschmack zu groß und der Gestaltungsfreiraum zu klein war. Will sagen, maschinell erzeugtes XML sieht wie maschinell erzeugtes XML aus, und ist für den Menschen nur schwer lesbar.

Inzwischen schreibe ich meine Serialisierungs- und Deserialisierungsmethoden liebevoll von Hand und bin zufrieden damit.
pyseidon
User
Beiträge: 19
Registriert: Donnerstag 24. September 2009, 20:25

@cofi: Es kommen alle relevanten Attribute in `__init__` vor.

@snafu: Sowas kam mir auch in den Sinn. Muss ich mal schauen ob sich der Aufwand lohnt.

@pillmuncher: Hmm, den Visitor Pattern könnte ich mir auch noch anschauen.

@Sirius3: Die Reihenfolge ist relevant, soll ja valides XML herauskommen. Die Namensgebung ist ehr nicht das Problem. Da kann ich einfach ein `upper()` benutzen und schon habe ich zu dem Attribut (lower_case_with_underscores) den entsprechenden XML-Element-Namen (UPPER_CASE_WITH_UNDERSCORES). Bei Klassennamen müsste ich das CamelCase umwandeln, sollte aber kein Problem sein.

Ja, bei über 100 Klassen macht das Spaß die XML-Representationen selber zu schreiben. :) Wollte mir da mit einem generischen Ansatz die Arbeit etwas vereinfachen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyseidon: Du hast also eine Vorgabe, wie das XML aussehen soll. Dann mußt Du also alle Deine Klassen den Vorgaben entsprechend schreiben, darfst keine zusätzlichen (internen) Attribute, hinzufügen, darfst nicht mit Properties und anderem nicht-__dict__ basiertem Zeugs arbeiten. Kurz, Du bist in der Art, wie Du Deine Python-Klassen programmierst sehr stark eingeschränkt. Und das Problem der Typisierung ist auch nicht gelöst. Das findet vielleicht ein Java-Programmierer toll, aber ein Pythonprogrammierer würde den Job wechseln. Sauberer ist es eine generische Abstraktionsschicht hinzuzufügen, oder gleich alles händisch zu machen. Der Aufwand ist vielleicht etwas größer, die gewonnene Freiheit wiegt das allemal wieder auf. Und die Tagnamen sind wirklich GROSS_BUCHSTABEN_MIT_UNDERSTRICH. Dann zählt das Argument, dass das irgendein Mensch lesen oder schreiben will, wirklich nicht mehr.

Oder bist Du frei in der Art wie das XML aussehen soll und wie es validiert wird. Sowohl XSD als auch Relax-NG erlauben es, dass die Reihenfolge der Tags beliebig sein kann. Und die übliche XML-Namenskonvention ist lowercase-with-hyphen.
pyseidon
User
Beiträge: 19
Registriert: Donnerstag 24. September 2009, 20:25

@Sirius3: Ich muss mich an ein standardisiertes Austauschformat halten. Wie ich da meine Klassen schreiben ist (fast) vollkommen egal. Es muss eben das korrekte XML herauskommen. Das Format ist durchaus zum menschlichen Lesen gedacht, sonst hätte man das Ganze Format anders aufziehen können.
Und die übliche XML-Namenskonvention ist lowercase-with-hyphen.
Naja, mit dem üblich ist das so eine Sache. Ist mir ehrlich gesagt ganz selten untergekommen. :)
Antworten