Config-Datei automatisch parsen

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
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

Hallo zusammen,

ich suche eine Möglichkeit eine Config-Datei mit Variablen automatisch einzulesen, damit ich sie in python weiterverwenden kann

Die Config.ini sieht so aus:

Code: Alles auswählen

[Default]
TRCexe = 'Anwendung.exe'
TRCpath[] = 'C:\\Program Files\\.......\\ ;Array[0]'
TRCpath[] = 'C:\\AlternativerPfad\\ ;Array[1]'
checktimer = 180 ;seconds
....
Nun habe ich probiert das ganze mit dem ConfigParser einzuladen, nur komme ich da nicht drauf die Variablen automatisch zu übernehmen. Hat hierzu einer eine Idee? Derzeit lese ich nur Strings ein...

Code: Alles auswählen

config = ConfigParser.ConfigParser()
config.read("config.ini")
configItems = config.items("Default") #gibt mir natürlich nur ein Array mit Strings aus
BlackJack

@TechnoFeather: Es gibt da verschiedene `get*()`-Methoden. Und das mit dem ”Array” funktioniert nicht mit `ConfigParser`, da bleibt immer der letzte Einzelwert vorhanden wenn ein Schlüssel in einem Abschnitt mehr als einmal vorkommt.
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

Habs probiert, aber irgendwie klappt das nicht.

Also ich möchte einmal das die Ini-Datei nach Namen & Werten durchsucht wird. Habe ich aktuell mit SafeConfigParser(), hier bekomme ich allerdings nur Werte. Wie man einen Namen aus einem Wert definiert finde ich einfach nicht heraus.

config.ini:

Code: Alles auswählen

[Default]
myName = "Peter"
Python-Code:

Code: Alles auswählen

    parser = SafeConfigParser()
    parser.read('config.ini')
    for section_name in parser.sections():
        print 'Section:', section_name
        print '  Options:', parser.options(section_name)
        for name, value in parser.items(section_name):
            print '  %s = %s' % (name, value)
        print ""
    print myName
Konsole:

Code: Alles auswählen

Section: Default
  Options: myname
  myname = "Peter"

Error: name 'myName' is not defined
ich verstehe nicht so ganz, dass es so umständlich ist Variablen/Namen aus einer config.ini zu ziehen? Ich möchte später lediglich die Möglichkeit haben weitere Suchpfade in der config.ini zu definieren und Werte wie Timeouts, Pausen, etc. zu verändern. Ich kann mir dabei einfach nicht vorstellen alle einzeln über myName = parser.get('Default', 'myname') definieren zu müssen - das muss doch auch effizienter gehen?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@TechnoFeather: Erwartest du ernsthaft, dass Python einen Mechanismus eingebaut hat, der vollautomatisch erraten kann, dass der Name in einer zuvor geöffneten Config-Datei gemeint ist, wenn du auf den Namen `myName` im Code zugreifen willst? Es wurde doch nirgendwo definiert, was `myName` aus Python-Sicht bedeuten soll. Klar, dass dir das um die Ohren fliegt. Es wäre auch sehr merkwürdig, wenn das anders wäre.

Was du suchst, ist wahrscheinlich dies hier:

Code: Alles auswählen

myName = parser.items(section_name)['myName']
EDIT: Sorry, `parser.items()` liefert kein Wörterbuch, sondern eine Liste zurück. Du kannst aber das Ergebnis mittels `dict(parser.items(section_name))` konvertieren und anschließend auf die gewünschten Namen zugreifen.
Zuletzt geändert von snafu am Dienstag 21. April 2015, 10:49, insgesamt 1-mal geändert.
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

aber was ist, wenn ich "myName" dynamisch definiert haben möchte?

Python weiß ja eigentlich was ich will, sonst würde der Code for name, value in parser.items(section_name): ja nicht funkionieren.

In meinem Kopf müsste es so funkionieren:

Code: Alles auswählen

    parser = SafeConfigParser()
    parser.read('config.ini')
    parser.optionxform = str

    for section_name in parser.sections():
        print 'Section:', section_name
        print '  Options:', parser.options(section_name)
        for name, value in parser.items(section_name):
            defineVariable(name, value) #diese Funktion scheint es nicht zu geben?
    print "myName: " + myName # >>>myName: "Peter"
BlackJack

@TechnoFeather: Nein so eine `defineVariable()`-Funktion gibt es nicht. $GOTT sei Dank. Man möchte sich nicht beliebige Namen in den eigenen Namensraum kippen lassen über die man keine Kontrolle hat. Damit kann man dann ganz leicht das Programm kaputt machen. Zum Beispiel in dem man ``open=yes`` in die Konfigurationsdatei schreibt und das dann die `open()`-Funktion verdeckt wenn das so ginge wie Du Dir das vorstellst. Man müsste bei der Konfigurationsdatei plötzlich aufpassen welche Namen im Programm verwendet werden und beim Programmieren das man ja keinen Namen verwendet der auch in der Konfigurationsdatei steht.

Der vorgesehene Weg ist sich die Werte mittels der `get*()`-Methoden vom `ConfigParser`-Objekt zu holen. Und zwar einzeln wenn sie an lokale Namen gebunden werden sollen.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

TechnoFeather hat geschrieben:aber was ist, wenn ich "myName" dynamisch definiert haben möchte?
Warum sollte man das wollen? Entweder man nimmt sich den Wert für einen ganz bestimmten Namen und verwendet auch für die Variable eine spezifische Bezeichnung. Oder man holt sich alle Werte in einer Schleife und bindet die Zwischenergebnisse an etwas allgemeines wie `name`. Und wenn man nur eine Auswahl treffen will, dann legt man halt fest, welche Namen benötigt werden (z.B. mit einem Set oder einer Liste) und guckt durch Überprüfen von `dict(parser.items(section_name))`, was dementsprechend weggeworfen werden kann. Wobei das mit dem Umwandeln in ein Wörterbuch nicht zwingend erforderlich ist. Man kann die Liste ja auch direkt durchlaufen.
TechnoFeather hat geschrieben:Python weiß ja eigentlich was ich will, sonst würde der Code for name, value in parser.items(section_name): ja nicht funkionieren.
Python wendet einfach das sogenannte "Tuple Unpacking" an. Dies funktioniert, weil `parser.items()` eben eine Liste liefert, die als Elemente jeweils 2-elementrige Tupel hat. Dieses Vehalten ist dokumentiert, vorhersehbar und hat nichts mit deinem von mir kritisierten Vorhaben bezüglich dynamischer Variablen zu tun. Diese Idee ist unschön und bringt Nachteile, wie BlackJack ja schon erläutert hat. Du solltest dich wirklich davon verabschieden.
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

Ich verstehe nicht wieso das vorhaben auf so viel wiederstand stößt. Wenn ihr eine config.ini-Datei mit 100 definitionen und Daten habt, möchtet ihr diese einzeln dann definieren? Dies kann doch auch der Rechner beim durchlaufen erledigen.

Im übrigen habe ich es jetzt geschafft. Vielleicht sollte ich dazu sagen, dass das Programm nur von mir, bzw. einem anderen Admin ausgeführt wird und wir eh Vollzugriff auf den Rechner haben.

Gelöst habe ich es mittels exec. Mir ist bewusst, dass diese Art nicht grade die feinste ist, allerdings wird das Programm wie gesagt in einer sicheren Umgebung ausgeführt. Auch die exec-Anweisung ist "begrenzt" und wird durch try und except eingerenzt. Daher weiß ich nicht wofür der ganze trubel? :K In die weite Welt verkaufen möchte ich das Skript nicht - von daher....
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und die Tatsache, nun hundert verschiedene Namen definiert zu haben, die einem möglicherweise wichtige Python-Funktionen überschrieben haben und einfach nur den globalen Namensraum zumüllen hat jetzt genau welchen Vorteil gegenüber einem Wörterbuch?

Aber nun gut. Vermutlich bist du eh nicht mehr von deinem Vorhaben abzuhalten. Geht mir manchmal auch so. Vielleicht kommt ja irgendwann in deinem späteren Programmier-Leben die Erkenntnis, dass die Verwendung von `eval()` hier doch keine so gute Idee war...
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@TechnoFeather: das hat doch nichts mit Widerstand zu tun. Wie BlackJack schon geschrieben hat, möchte ein Programmierer gerne die Kontrolle darüber behalten, welche Variablen definiert werden, weil ansonsten beliebige Fehler auftreten können. Wenn man einen Wert nur einmal braucht, dann ist es ja kein Problem section['myName'] zu schreiben, wenn man ihn mehrfach braucht, stört eine "myname = section['myname']"-Zeile auch nicht weiter.

"exec" zu benutzen ist in diesem Fall vielleicht kein Sicherheitsrisiko, aber definitiv eine Fehlerquelle, da hilft auch kein try-except, wie auch immer Du glaubst, das hier sinnvoll eingesetzt zu haben. Das Programm ist einfach nur kaputt: nicht testbar und nicht wartbar.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, mit Widerstand hat es ja schon was zu tun. Allerdings mit berechtigtem Widerstand. Wobei die Widerständler ihren Widerstand erfahrungsgemäß so gut wie immer als berechtigt ansehen. Liegt sozusagen in der Natur der Sache. :)
BlackJack

@TechnoFeather: Es gibt zum Beispiel technischen Widerstand: Man kann innerhalb von Funktionen keine neuen Variablen einführen. Das ist technisch nicht möglich. Bleibt also nur der Modulnamensraum, und da gibt es aus den bereits genannten Gründen Widerstand — man holt sich in den Namensraum nicht einfach unkontrolliert Namen. Wenn man das sauber lösen möchte dann braucht man einen eigenen Namensraum für die Namen und Werte. *Dafür* braucht man aber nur ein Objekt und eine Schleife und kein ``exec``. Ich sehe aber auch nicht wirklich welches Problem da eigentlich gelöst werden soll. Einfacheren Zugriff auf das `ConfigParser`-Objekt kann man sich auch mit einer kleinen Wrapperklasse schreiben ohne zur Laufzeit Quelltext generieren, parsen, und ausführen zu müssen, was fast immer ein Zeichen ist das man zu faul war sich eine richtige Lösung auszudenken und die *Programmiersprache* zu verwenden.
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

Außerhalb einer Funktion kann man doch aber die Variablen hinzufügen, oder? Hier wird mein Parser ausgeführt, es ist quasi das erste was bearbeitet wird.

Vielleicht nur einmal um meine Seite noch einmal genauer zu beschreiben:
Mein Programm prüft ob eine Anwendung einen Fehler meldet oder überhaupt ausgeführt wird. Dieses Programm wird auf mehreren Rechnern ausgeführt. Nun kann es aber durchaus vorkommen, dass sich der Anwendungsname oder der Pfad zur Anwednung sich ändert oder diese sogar variieren, da es wie gesagt auf mehreren Systemen zum einsatz kommt. Hier fand ich die Lösung, die Pfade und den Dateinamen über eine Config-Datei zu managen sehr schön:

Code: Alles auswählen

[Default]
Pfad_0 = "C:\\........\\"
Pfad_1 = "D:\\........\\"
Pfad_2 = "M:\\........\\"
Anwendung_0 = "Programm_64bit.exe"
Anwendung_1 = "Programm.exe"
Timeout = 12
MaxErrorcount = 3
....
Hier kann es aber durchaus sein, dass sich auf einem (!) Rechner der Pfad ändert oder die Programmversion zusätzlich eine andere ist. Hierfür möchte ich dann in die Config-Datei ein 'Pfad_3 = "X:\\.......\\"' oder ein 'Anwendung_2 = "Programm_v2.exe"' einfügen und sonst nichts. Wenn ich dann dafür in die py-Datei einsteigen muss, um später mittels py2exe das Skript in eine eigenständige Anwendung umzuwandeln, dann bräuchte ich keine config.ini und es wäre auch sehr mühselig, vielleicht nur wegen einer kleinen Änderung im Pfad.

Klar kann man in der Config.ini fehler machen, aber diese werden einem dann so gut es geht mitgeteilt - soweit zumindest der Plan. In der python-Datei werden zudem am Anfang Defaultwerte geladen, so dass wenn mal die Datei verloren geht, nicht das ganze nie wieder funktioniert. Und selbst wenn würde es nicht darin ausarten, dass alles einfriert und man ohne infos dasteht, da auch alles in einer Logdatei geschrieben wird.

Code: Alles auswählen

    parser = SafeConfigParser()
    parser.optionxform = str #Legt fest, dass der eingelesene String CaseSensitive ist (Default: LowerCase)
    parser.read('config.ini')

    for section_name in parser.sections():
        print "config.ini Data:"
        for name, value in parser.items(section_name):
            print '  %s = %s' % (name, value)
            try: #erst versuchen einen integer zu bekommen
                exec("%s=%i" % (name,value))
            except:
                pass #wenn das nicht funktioniert....
            try: #.....dann versuchen einen String zu bekommen
                exec("%s=%s" % (name,value))
            except SyntaxError:
                print ""
                print "  SyntaxError while loading the config.ini. Datafields must be a integer or a string"
                print ""
            except:
                print ""
                print "  Unexpected error while loading the config.ini - please check the file."
                print ""
        print
Danach versuche ich grade "Pfad_n" zu einem "Pfad = [......, ....., .....]" zusammenzufügen, damit ich alle Pfade nach einander prüfen kann.


Sicherlich gibt es viele schönere Lösungen, aber ich bin erstmal insgesamt froh darüber, dass das Skript läuft und auch mehrere Tage im Dauerlauf überstanden hat. Nichts desto trotz interessieren mich natürlich aber auch die besseren Lösungen, da ich noch ein Python-Anfänger bin. Bevor einer aber sagt, dass es nicht geht und das ich das Skript so nicht ausführen sollte, lasse ich es lieber so und freue mich, dass die gewünschte Anwendung dauerhaft kontrolliert und korrekt neugestartet wird.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@TechnoFeather: Dass das nicht geht, hat niemand behauptet. Es ist aber ziemlich fehleranfällig.

Und ich habe immer noch nicht verstanden, wieso es globale Namen sein müssen. Kann es sein, dass der Programmcode vor der Config-Datei existiert hat und du nun automatisiert spezifische Werte an die Namen binden willst?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und hier mal ein Auszug aus einer interaktiven Python-Shell Sitzung, wo ich einen Namen global definiere, ohne `eval()` zu benutzen:

Code: Alles auswählen

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> globals()['x'] = 123
>>> x
123
Oder in einem Rutsch:

Code: Alles auswählen

>>> names = {'a': 1, 'b': 2, 'c': 3}
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> globals().update(names)
>>> a
1
>>> b
2
>>> c
3
Aber ich betone nochmal: Das ist äußerst unüblich und man sollte schon sehr gute Gründe haben, um so zu verfahren.
TechnoFeather
User
Beiträge: 20
Registriert: Freitag 24. Juli 2009, 11:07

Ja, der Programmcode hat vorher existiert und funktioniert. Dann kam mir der Gedanke, dass es sehr gut sein kann, dass sich der Anwendungspfad und Datei im laufe des Jahres noch ändern könnte. Daher die Idee das mittels Config.ini zu lösen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@TechnoFeather: genau in Deinem Fall willst Du ja gar nicht alle Pfade und Anwendungen durchnummeriert als Variablen haben, sondern in einer passenden Struktur, etwa:

Code: Alles auswählen

pfade = {int(n.split('_',1)[1]):v for n,v in parser.items('Default') if n.startswith('Pfad_')}
anwendungen = {int(n.split('_',1)[1]):v for n,v in parser.items('Default') if n.startswith('Anwendung_')}
und mit

Code: Alles auswählen

max_error_count = parser.getint('Default', 'MaxErrorcount')
ist auch jedem, der das Programm liest sofort klar, dass max_error_count aus der Config-Datei kommt und ein int ist.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich sehe das ähnlich wie Sirius3: Die Stellen, die variieren, kann man leicht mit den passenden Gettern des `ConfigParser`s ersetzen. Dann braucht es noch 2 Zeilen, um ein `ConfigParser()`-Objekt zu erstellen und die passende Datei einzulesen und das war's. Das ist tausendmal transparenter und wartungsfreundlicher als ein automatisiertes Herumpfuschen im globalen Namensraum.
Antworten