INI-Datei einlesen, trotz fehlenden Sektionen/Optionen

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.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

bevor ich an die frische Luft gehe und einen Spaziergang mache, möchte ich ein kleines Problem los werden. Ich habe ein kleines ausführbares Beispielprogramm geschrieben (siehe unten). Es geht darum, eine INI-Datei einzulesen bzw. gegebenenfalls eine INI-Datei zu erstellen.

Kurz zur Beschreibung: Wir sehen eine ConfigurationSaver()-Klasse. Dort habe ich für den Beispiel ein Wörterbuch gespeichert. Auffällig ist, dass in der write_ini_file()-Funktion absichtlich nur drei Sektionen mit den dazugehörigen Optionen geschrieben werden. In der read_ini_file() werden absichtlich 4 Sektionen mit den dazugehörigen Optionen gelesen - zumindest versucht. Es liegt in der Natur, dass dadurch in der Exception Fehler aufgeworfen werden. Diese werden auch abgefangen (seht mir an dieser Stelle nach, wenn ich für den Beispiel die Fehler nicht ordnungsgemäß behandle).

Jetzt zum Problem: Wenn ich also versuche die INI-Datei einzulesen, bricht mir das Programm ab. Logisch, denn es konnte keine Option namens fibonacci gefunden werden. Aber die Prozedur bricht für meinen Geschmack zu früh ab. Das heißt, die anderen Werte, die in der INI-Datei sind, werden nicht eingelesen, in das Wörterbuch gespeichert und angezeigt. Meine Idee ging dahin, dass, wenn eine Option fehlt, trotzdem die vorhandenen Werten in das Wörterbuch gespeichert werden.

Im alltäglichen Leben ist es üblich, dass das Programm eine neuere Version ist, aber die INI-Datei noch eine ältere Version ist. Das führt bekanntlich dazu, dass in der INI-Datei sowohl Optionen also auch Sektionen fehlen könnten. Ich glaube Sirius3 war es, der mich darauf hinwies, dass die INI-Datei erst dann modifiziert werden soll, wenn der Anwender es sich wünscht. Das heißt also, dass mein Programm die INI-Datei soweit einlesen soll, wie es ihm möglich ist, auch wenn Sektionen und Optionen fehlen.

Nun stecke ich gedanklich fest. Ich frage mich, was ich bei der Fehlerbehandlung anstellen soll, damit mein Programm "weiter" mach und die ihm bekannten Werte einliest und quasi auf die fehlenden Optionen und Sektionen erst einmal verzichtet. Dafür werden ja bestimmte Werte in das Wörterbuch "hart kodiert" - die eben als "Detault-Werte" dienen sollen.

Code: Alles auswählen

import ConfigParser
import os
import sys
import traceback

class ConfigurationSaver(object):
        def __init__(self):

            self.dict_general_settings = {
                                "Key1": "False",
                                "Key2": "False",
                                "Key3": "False",
                                "Key4": "False"
                                            }


def check_file_exists(ini_file_path):
    with open(ini_file_path, "r") as config_file: pass
    
def write_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name = "SECTION_1"

    with open(ini_file_path, "w") as config_file:
        
        config_parsing.add_section(section_name)

        config_parsing.set(section_name, 'Foo', dict_settings.dict_general_settings["Key1"])
        config_parsing.set(section_name, 'Bar', dict_settings.dict_general_settings["Key2"])
        config_parsing.set(section_name, 'FiBaBo', dict_settings.dict_general_settings["Key3"])

        config_parsing.write(config_file)

def read_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name = "SECTION_1"
        
    config_parsing.read(ini_file_path)

    dict_settings.dict_general_settings["Key1"] = config_parsing.get(section_name, 'Foo')

    dict_settings.dict_general_settings["Key2"] = config_parsing.get(section_name, 'Bar')
    
    dict_settings.dict_general_settings["Key3"] = config_parsing.get(section_name, 'FiBaBo')

    dict_settings.dict_general_settings["Key4"] = config_parsing.get(section_name, 'fibonacci')
    

    print "Foo", dict_settings.dict_general_settings["Key1"]
    print "Bar", dict_settings.dict_general_settings["Key2"]
    print "FiBaBo", dict_settings.dict_general_settings["Key3"]
    print "FiBaBo", dict_settings.dict_general_settings["Key4"]
                                                                                
if __name__ == "__main__":
    
    parsing_config = ConfigParser.SafeConfigParser()

    dict_settings = ConfigurationSaver()
    
    BASE_PATH = os.path.dirname(os.path.abspath(__file__))
    INI_FILE_PATH = os.path.join(BASE_PATH, 'config.ini')

    try:
        
        check_exists_file(INI_FILE_PATH)

        try:
            
            read_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
            
        except ConfigParser.NoSectionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoSectionError:", desired_trace

        except ConfigParser.NoOptionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoOptionError:", desired_trace

    except (OSError,IOError), e:
        write_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: eine Exception tritt dort auf, wo der Fehler ist und wird dort abgefangen, wo der passende except-Block steht. Deshalb sollten try-Blöcke so klein wie möglich sein.
Was erwartest Du und wo weicht diese Erwartung von dem ab, was Python macht?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich dachte, es ging bereits aus meinem langen Text hervor, was ich erwarte und was passiert. Aber ich versuche es noch einmal zu beschreiben. In meinem Beispiel-Programm wird eine Situation simuliert. Es werden absichtlich nur 3 Sektionen/Optionen geschrieben. Gelesen werden aber 4 Sektionen/Optionen. Auf diese Weise möchte ich folgendes simulieren: Das Programm ist eine Version höher, die INI-Datei jedoch ist älter. Immerhin fehlen Sektionen/Optionen, die aber das neue Programm bereits kennt.

In meinem Fall werden die entsprechenden Fehler auch ordnungsgemäß abgefangen. Das heißt, es wird beim Einlesen naturgemäß aufgezeigt, dass Sektionen/Optionen fehlen. Soweit so gut. Nun stecke ich mit meinen Gedanken fest. Ich möchte, dass trotz fehlender Sektionen/Optionen mein Programm die anderen Werte aus der INI-Datei liest. Aber in meiner Version unterbricht mein Programm gänzlich, sobald Sektionen/Optionen fehlen. Das heißt, ich komme nicht an die übrigen Werte in der INI-Datei heran. Die Werte werden erst dann komplett eingelesen und in das Wörterbuch gespeichert, sobald alles nach Plan läuft. Jetzt frage ich mich, wie ich das Problem lösen könnte. Mir fehlt also der gedankliche Anstoß.

Zu meinem Try-Block. Was spricht grundsätzlich gegen verschachtelte Try-Blöcke? Im ersten Try-Block wird ja versucht auf eine Datei zu zugreifen. Wird dort ein Fehler aufgeworfen, wird eben eine INI-Datei erstellt. Wenn alles gut ist, geht es weiter, und zwar mit dem Einlesen. Ich gebe zu, in meinem Beispiel wurden nicht alle Sicherheitsmechanismen berücksichtigt. Ist auch unwichtig, da ich nur mein gedankliches Problem verdeutlichen möchte.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Deine check_file_exist Funktion ist sinnlos. Du gehst doch sonst nicht davon aus dass sich die Welt nicht verändert während du nicht hinschaust, wieso schreibst du Code als ob Dateisysteme davon ausgenommen sind?

Ansonsten lässt sich deine Frage durch lesen der Dokumentation beantworten. Schau dir mal die Argumente an die du an die ConfigParser Klasse übergeben kannst.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Zwei mal nicht in einem Satz ist verwirrend. Was möchtest du mir genau sagen? Es ist sinnlos danach zu schauen, ob eine Datei existiert? Wieso? Und der Hinweis auf die Seite war führ mich auch weniger hilfreich. In welchem Abschnitt wird ein äquivalenter Lösungsansatz geschildert?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Sophus hat geschrieben:Es ist sinnlos danach zu schauen, ob eine Datei existiert?
Du schaust nicht ob eine Datei existiert, du schaust ob du eine Datei an dem Zeitpunkt an dem du versuchst sie zu öffnen existiert und lesbar ist. Noch bevor du die Datei wieder schliesst kann die Datei schon nicht mehr existieren.
In welchem Abschnitt wird ein äquivalenter Lösungsansatz geschildert?
Du kannst Defaults an ConfigParser übergeben. Die werden dann von der Konfigurationsdatei überschrieben. Auf die Weise ist garantiert dass die Optionen die du erwartest alle vorhanden sind.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@DasIch: Ich glaube, ich weiß worauf du hinaus willst. Man könnte mit dem sogenannten "Context Manager" die Ziel-Datei solange geöffnet lassen, bis sozusagen die Arbeit erledigt ist. Auf diese Weise geht man einen sicheren Weg, dass die Datei auch während der Arbeit auch existiert. Habe ich dich richtig verstanden?

Zu deinem Verweis. Meintest du etwa diese Stelle?

Code: Alles auswählen

import ConfigParser

# New instance with 'bar' and 'baz' defaulting to 'Life' and 'hard' each
config = ConfigParser.SafeConfigParser({'bar': 'Life', 'baz': 'hard'})
config.read('example.cfg')

print config.get('Section1', 'foo') # -> "Python is fun!"
config.remove_option('Section1', 'bar')
config.remove_option('Section1', 'baz')
print config.get('Section1', 'foo') # -> "Life is hard!"
Auf meinem Beispiel-Quelltext angewendet heißt das, ich muss mein Wörterbuch modifizieren? Ich kann ja schlecht das ganze Wörterbuch übergeben? Das heißt also, ich muss dann schauen , welche Sektionen/Optionen fehlen, und dann die passenden Stellen aus meinem derzeitigen Wörterbuch rausnehmen?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@DasIch: Ich habe mal deinen Verweis durchstudiert und habe meinen Quelltext wie folgt geändert (Ich habe jetzt mal ganz bewusst die anderen Anmerkungen nicht berücksichtigt, da dies hier nicht von Bedeutung ist).

Was habe ich gemacht? Ich habe folgende Zeile verändert:

Code: Alles auswählen

    parsing_config = ConfigParser.SafeConfigParser({"fibonacci": "False_4"})
Ich übergebe der SafeConfigParser()-Klasse Standard-Werte. Das Problem hierbei ist, dass ich jedoch nicht weiß, wie alt die INI-Datei ist. In diesem Beispiel weiß ich ja gezielt, welche Sektion/Option fehlt und konnte gezielt übergeben. Aber was ist, wenn mein Programm drei Versionen neuer ist, aber die INI-Datei beim Anwender immer noch drei Versionen zurückliegen, da er/sie bisher keine weiteren Einstellungen vorgenommen hat?

Code: Alles auswählen

import ConfigParser
import os
import sys
import traceback

class ConfigurationSaver(object):
        def __init__(self):

            self.dict_general_settings = {
                                "Key1": "False_1",
                                "Key2": "False_2",
                                "Key3": "False_3",
                                "Key4": "False_4"
                                            }


def check_file_exists(ini_file_path):
    with open(ini_file_path, "r") as config_file: pass

def write_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name = "SECTION_1"

    with open(ini_file_path, "w") as config_file:
        
        config_parsing.add_section(section_name)

        config_parsing.set(section_name, 'Foo', dict_settings.dict_general_settings["Key1"])
        config_parsing.set(section_name, 'Bar', dict_settings.dict_general_settings["Key2"])
        config_parsing.set(section_name, 'FiBaBo', dict_settings.dict_general_settings["Key3"])

        config_parsing.write(config_file)

def read_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name = "SECTION_1"
        
    config_parsing.read(ini_file_path)

    dict_settings.dict_general_settings["Key1"] = config_parsing.get(section_name, 'Foo')

    dict_settings.dict_general_settings["Key2"] = config_parsing.get(section_name, 'Bar')
    
    dict_settings.dict_general_settings["Key3"] = config_parsing.get(section_name, 'FiBaBo')

    dict_settings.dict_general_settings["Key4"] = config_parsing.get(section_name, 'fibonacci')
    

    print "Foo", dict_settings.dict_general_settings["Key1"]
    print "Bar", dict_settings.dict_general_settings["Key2"]
    print "FiBaBo", dict_settings.dict_general_settings["Key3"]
    print "fibonacci", dict_settings.dict_general_settings["Key4"]
                                                                                
if __name__ == "__main__":
    
    dict_settings = ConfigurationSaver()
    
    parsing_config = ConfigParser.SafeConfigParser({"fibonacci": "False_4"})
    
    BASE_PATH = os.path.dirname(os.path.abspath(__file__))
    INI_FILE_PATH = os.path.join(BASE_PATH, 'config.ini')

    try:
        
        check_file_exists(INI_FILE_PATH)

        try:
            
            read_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
            
        except ConfigParser.NoSectionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoSectionError:", desired_trace

        except ConfigParser.NoOptionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoOptionError:", desired_trace

    except (OSError,IOError), e:
        write_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ok, ich bin der Sache etwas näher gekommen - hoffe ich zumindest.

Was habe ich gemacht? Ich habe eine AllOptions()-Klasse erstellt, und dort alle Optionen mit den dazugehörigen Standard-Werten in Form eines Wörterbuches gespeichert. Das diese Vorgehensweise keineswegs elegant ist, ist auffallend. Denn ich muss immer das Wörterbuch aus der AllOptions()-Klasse stets im Blick behalten, wenn neue Optionen hinzukommen oder welche abgeschafft werden.

Was ich mich allerdings frage, ist, wie verhält es sich mit fehlenden Sektionen? Ich habe absichtlich in der read_ini_file()-Funktion eine weitere Sektion hinzugefügt:

Code: Alles auswählen

    section_name_2 = "SECTION_2"
    dict_settings.dict_general_settings["Key5"] = config_parsing.get(section_name_2, 'bazz')
Diese steht absichtlich nicht in der INI-Datei.

Code: Alles auswählen

import ConfigParser
import os
import sys
import traceback

class ConfigurationSaver(object):
        def __init__(self):

            self.dict_general_settings = {
                                "Key1": "False_1",
                                "Key2": "False_2",
                                "Key3": "False_3",
                                "Key4": "False_4",
                                "Key5": "False"
                                            }

class AllOptions(object):
        def __init__(self):

            self.dict_general_settings = {
                                "Foo": "False_1",
                                "Bar": "False_2",
                                "FiBaBo": "False_3",
                                "fibonacci": "False_4",
                                "baz": "False"
                                            }
            
def write_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name = "SECTION_1"

    with open(ini_file_path, "w") as config_file:
        
        config_parsing.add_section(section_name)

        config_parsing.set(section_name, 'Foo', dict_settings.dict_general_settings["Key1"])
        config_parsing.set(section_name, 'Bar', dict_settings.dict_general_settings["Key2"])
        config_parsing.set(section_name, 'FiBaBo', dict_settings.dict_general_settings["Key3"])

        config_parsing.write(config_file)

def read_ini_file(config_parsing, ini_file_path, dict_settings):

    section_name_1 = "SECTION_1"
        
    config_parsing.read(ini_file_path)

    dict_settings.dict_general_settings["Key1"] = config_parsing.get(section_name_1 , 'Foo')

    dict_settings.dict_general_settings["Key2"] = config_parsing.get(section_name_1 , 'Bar')
    
    dict_settings.dict_general_settings["Key3"] = config_parsing.get(section_name_1 , 'FiBaBo')

    dict_settings.dict_general_settings["Key4"] = config_parsing.get(section_name_1 , 'fibonacci')

    section_name_2 = "SECTION_2"
    dict_settings.dict_general_settings["Key5"] = config_parsing.get(section_name_2, 'bazz')

    print "Foo", dict_settings.dict_general_settings["Key1"]
    print "Bar", dict_settings.dict_general_settings["Key2"]
    print "FiBaBo", dict_settings.dict_general_settings["Key3"]
    print "fibonacci", dict_settings.dict_general_settings["Key4"]

    
if __name__ == "__main__":
    
    dict_settings = ConfigurationSaver()
    dict_options = AllOptions()
    
    parsing_config = ConfigParser.SafeConfigParser(dict_options.dict_general_settings)
    
    BASE_PATH = os.path.dirname(os.path.abspath(__file__))
    INI_FILE_PATH = os.path.join(BASE_PATH, 'config.ini')

    try:
        
        check_file_exists(INI_FILE_PATH)

        try:
            
            read_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
            
        except ConfigParser.NoSectionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoSectionError:", desired_trace

        except ConfigParser.NoOptionError, err:
            desired_trace = traceback.format_exc(sys.exc_info())
            print "NoOptionError:", desired_trace

    except (OSError,IOError), e:
        write_ini_file(parsing_config, INI_FILE_PATH, dict_settings)
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Meine Idee wäre, der Anwendung eine Standardkonfiguration mitzugeben, welche durch den Benutzer nicht geändert werden soll. Der Benutzer verwendet eine (möglicherweise unvollständige) eigene Variante dieser Konfiguration.

Die Konfiguration zu erhalten bestünde aus drei Schritten:
  1. Standardkonfiguration einlesen
  2. Benutzerkonfiguration einlesen
  3. Standardkonfiguration non-permanent mit der Benutzerkonfiguration überschreiben/ergänzen
Dictionary bietet mit der update-Methode die Lösung für Schritt drei. Ohnehin ist es m. E. sinnvoll die Konfiguration als Dictionary zur Verfügung zu stellen.

Code: Alles auswählen

#!/usr/bin/env python3
import configparser


def get_configuration(filename):  # -> dict
    parser = configparser.ConfigParser()
    parser.read(filename)
    return {section: dict(parser.items(section))
            for section
            in parser.sections()}


def get_default_configuration():  # -> dict
    return get_configuration('default.ini')
    
    
def get_user_configuration():  # -> dict
    return get_configuration('user.ini')


def main():
    configuration = get_default_configuration()
    user_configuration = get_user_configuration()
    configuration.update(user_configuration)
    print(configuration)
    
    
if __name__ == '__main__':
    main()
Die zugrunde liegenden INI-Dateien:

Code: Alles auswählen

# This INI-file contains the default-configuration for our
# example-application and will be shipped with the program and
# should not be modified by the user.

[Application]
name=The Example Application
version=1.3.37

[Window]
width=800
height=600

Code: Alles auswählen

# This INI-file contains the user-configuration for our
# example-application and can be modified by the user and/or the
# application using some sort of settings-menu, etc.

[Window]
width=640
height=480

[Database]
host=localhost
user=me
Eine Funktion, welche die (nun fertige) Konfiguration verifiziert, kann nachgeschaltet werden, sollte m. E. jedoch nicht Bestandteil des Einlesens sein.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@bwbg: Ich habe zu deinen Ausführungen ein paar Fragen bzw. Anmerkungen. Zunächst einmal. Meine INI-Datei ist derzeit so gestaltet, dass die AnwenderInnen über die Benutzeroberfläche alles ändern können. Ich kam bisher noch an keine Situation, in der ich unveränderbare Einstellungen eingepflegt habe. Und sollte ich dort angelangen, würde ich das programm-intern lösen und das keineswegs in eine INI-Datei verlagern. Zum Beispiel werden Informationen zu meinem Programm intern in ein Wörterbuch gespeichert.

Aber nun zu meinem Problem. Ich bin ja schon soweit gekommen, dass ich dem ConfigParser Standard-Werte übergeben konnte. Bisher hat mir das auch gefallen. Aber aus der Situation heraus, dass die INI-Datei älter sein könnte als die Anwendung, kann es auch passieren, dass meine Anwendung nicht nur Optionen vermisst, sondern auch Sektionen. Entweder kamen neue Sektionen hinzu oder wurden abgeschafft. In meinem oben zuletzt genannten Quelltext wurde die Situation ja simuliert. Meine read-Funktion versucht aus der Sektion namens "SECTION_2" zu lesen, die aber in der alten INI-Datei fehlt. Das Problem der fehlenden Optionen ist ja geklärt. Nun frage ich mich, wie man fehlende Sektionen übergehen kann? Diese ebenfalls als Standard-Werte übergeben? Wie?
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Die vorgestellte Lösung erlaubt es dem Benutzer, alle Einstellungen zu ändern (zu überlagern) oder gar zu ergänzen. Die default.ini in diesem Fall gibt den Standard vor, welcher auch dann zum Tragen kommt, wenn die user.ini nicht existiert oder nicht vollständig (im Sinne von "nutze die Standardeinstellungen") ist.
Sophus hat geschrieben:Ich bin ja schon soweit gekommen, dass ich dem ConfigParser Standard-Werte übergeben konnte.
... was nun durch die default.ini übernommen wird.
Sophus hat geschrieben:Aber aus der Situation heraus, dass die INI-Datei älter sein könnte als die Anwendung, kann es auch passen, dass meine Anwendung nicht nur Optionen vermisst, sondern auch Sektionen. Das Problem der fehlenden Optionen ist ja geklärt. Nun frage ich mich, wie man fehlende Sektionen übergehen kann? Diese ebenfalls als Standard-Werte übergeben? Wie?
Dies wird nun ebenfall durch die default.ini abgedeckt.

Ich muss allerdings eingestehen, dass mein Vorschlag einen bug einhält. Die beiden Dictionaries müssen noch eine Ebene tiefer aktualisiert (dict.update) werden. Bisher würden ganze Sektionen ausgetauscht.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@bwbg: Allerdings sehe ich ein Problem: Was ist, wenn die default.ini-Datei ebenfalls verschwindet? Da wäre eine Lösung von internen Standard-Werten vom Vorteil. In meinem Fall werden ja die Standard-Werten der Optionen intern verwaltet.

Aber unabhängig davon, möchte ich ja auch Python lernen, und bin ein an einer Stelle in deinem Quelltext ins Stocken gekommen.

Code: Alles auswählen

def get_configuration(filename):  # -> dict
    parser = configparser.ConfigParser()
    parser.read(filename)
    return {section: dict(parser.items(section))
            for section
            in parser.sections()}
        
Ich versuche mal diese Funktion zu beschreiben, was die macht. Zunächst wird eine Instanz aufgebaut. Nun ist die ConfigParser()-Klasse über die Variable parser zugänglich. Im nächsten Schritt wird über der Variable (die eine Art Zugang ist) die read()-Methode aufgerufen. Die get_configuration()-Funktion gibt im Allgemeinen ein Wörterbuch zurück. Soweit so gut. Aber was hier alles auf einer Dichte passiert, verwirrt mich. Soweit mein Wissen reicht, wird in dem Wörterbuch ein Schlüssel gegeben, das ist hier der Begriff section. Nun sind wir im Bereich der Werte. Im Bereich, wo die Werte platziert werden wird durch die dict()-Funktion zunächst ein leeres neues Wörterbuch erstellt. Innerhalb der dict()-Funktion werden mittels der items.()-Methode alle Sektionen ausgelesen. Wir befinden uns immer noch im Wörterbuch. Nun folgt die For-Schleife. Die For-Schleife iteriert über die Sektionen. Und durch das Iterieren werden die Sektionen in das neue Wörterbuch gespeichert, die durch die dict()-Funktion erstellt wurde.

Ich bin mir ziemlich sicher, dass ich nicht alles verstanden habe. Das Problem dabei ist immer, wenn alles in einer so dichten Zeile alles geschrieben wird, übersteigt das ganz schnell Denkvermögen. Daher versuche ich das auch alles zu verstehen.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Das Stichwort lautet hier dict-comprehension (DC). Sie funktioniert ähnlich wie die list-comprehension (LC). Nur liefert sie ein neues Dictionary statt einer Liste.

Hier mal einige Beispiele für LC und DC:

Code: Alles auswählen

>>> xs = [1, 2, 3, 4, 5]
>>> [x for x in xs]
[1, 2, 3, 4, 5]
>>> [x * 2 for x in xs]
[2, 4, 6, 8, 10]
>>> pairs = [('a', 1), ('b', 2), ('c', 3)]
>>> [x for x in pairs]
[('a', 1), ('b', 2), ('c', 3)]
>>> [(x, y) for x, y in pairs]
[('a', 1), ('b', 2), ('c', 3)]
>>> [('FOO_' + x, y * 2) for x, y in pairs]
[('FOO_a', 2), ('FOO_b', 4), ('FOO_c', 6)]
>>> {key: value for key, value in pairs}
{'c': 3, 'a': 1, 'b': 2}
>>> {'FOO_' + key: value * 100 for key, value in pairs}
{'FOO_a': 100, 'FOO_c': 300, 'FOO_b': 200}
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@bwbg: Ich habe mal versucht den Abschnitt zu verstehen, und nach meinem Stil geschrieben. Allerdings weiß ich nicht, ob es das gleiche tut oder ob es nur ein Zufall ist.

Ich habe den Abschnitt soweit zerpflückt, bis ich ihn auch verstehe. Ich habe hier also die For-Schleife erst einmal hervorgeholt. Ich iteriere hier also zunächst über die Sektionen. Jetzt bin ich im Rumpf der For-Schleife. In der Print-Anweisung erstelle ich ein Wörterbuch. Als Schlüssel wird hier wieder section genommen. In diesem Fall verstehe ich, woher section kommt, und zwar aus der For-Schleife. Im Werte-Bereich benutze ich die dict()-Funktion. Durch diese Funktion werden die einzelnen Elemente (items) aus den jeweiligen Sektionen (section) in ein Wörterbuch umgewandelt. Würde ich aber kein Wörterbuch mit den geschweiften Klammern erstellen, würden dann einzelne Wörterbücher entstehen. Je nach dem wie viele Sektionen es gibt. Bei drei Sektionen wären es dann drei Wörterbücher und so fort. Daher packst du alle Wörterbücher zunächst in ein Wörterbuch.

Ich hoffe, ich konnte es jetzt verstehen.

Code: Alles auswählen

    parser = configparser.ConfigParser()
    parser.read(filename)
    for section in parser.sections():
        print {section:dict(parser.items(section))}
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: die Default-ini kann ja gleich neben Deiner py-Datei liegen, dort geht sie dann nicht verloren, oder sie steht gleich direkt in Deinem Python Programm:

Code: Alles auswählen

import configparser
from io import StringIO

DEFAULT_INI = """
[Application]
name=The Example Application
version=1.3.37

[Window]
width=800
height=600
"""

def read_configuration(filename):
    parser = configparser.ConfigParser()
    parser.readfp(StringIO(DEFAULT_INI))
    parser.read(filename)
    return parser

def main():
    configuration = read_configuration('user.ini')
    print(configuration.get('Window', 'width'))
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Danke für deinen Vorschlag. Der klingt wirklich sehr gut. Ich werde mir den Quelltext in aller Ruhe ansehen. Allerdings habe ich jetzt schon eine kleine Frage. Ich habe bisher noch nicht mit dem Modul "StringIO" gearbeitet. Wenn ich mal Galileo Computing vertrauen darf, wird ein String wie eine Datei behandelt - quasi wie eine Pseudo-Datei? In deinem Beispiel wird also so getan, als sei DEFAULT_INI eine Datei, obwohl es sich hierbei um einen sogenannte DocString handelt. Richtig?
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: ja mit StringIO kann man einen String in ein Objekt verwandeln, das sich wie eine Dateiobjekt verhält. Wobei das kein Doc-String ist, nur weil er über mehrere Zeilen geht. Doc-Strings sind Stringliterale, die am Anfang eines Moduls, einer Klasse oder einer Funktion stehen und zur Dokumentation dienen.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

ConfigParser ist übrigens mit Python 3 offensichtlich irgendwann mal wesentlich besser geworden. Es fehlen allerdings auch in der Python 3 Version brauchbare Typen. Ich würde es deswegen in etwa so nutzen:

Code: Alles auswählen

from configparser import ConfigParser


CONFIGURATION_SCHEMA = {
    'Application': {
        'name': {'default': 'The Example Application'},
        'version': {'default': '1.3.37'}
    },
    'Window': {
        'width': {
            'default': '800',
            'type': 'int'
        },
        'height': {
            'default': '600',
            'type': 'int'
        }
    }
}


def get_default_configuration(schema=CONFIGURATION_SCHEMA):
    return {
        section: {
            option: option_schema['default']
            for option, option_schema in section_schema.items()
        }
        for section, section_schema in schema.items()
    }


def get_typed_configuration(parser, schema=CONFIGURATION_SCHEMA):
    def get_typed_value(parser, section_name, option_name, option_schema):
        type = option_schema.get('type', '')
        getmethod = getattr(parser, 'get{}'.format(type))
        return getmethod(section_name, option_name)

    return {
        section: {
            option: get_typed_value(parser, section, option, option_schema)
            for option, option_schema in section_schema.items()
        }
        for section, section_schema in schema.items()
    }


def get_configuration(filename, schema=CONFIGURATION_SCHEMA):
    parser = ConfigParser()
    parser.read_dict(get_default_configuration(schema=schema))
    parser.read([filename])
    return get_typed_configuration(parser, schema=schema)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@DasIch: Kann es sein, dass du mich gerne folterst? :D Ich bin doch nur ein lausiger Anfänger. Deine Ausführung wirkt sehr komplex und kompliziert. Jedoch werde ich versuchen deinen Ansatz zu verstehen. Bisher fand ich die Ansätze von bwbg und Sirius3 interessant. Immerhin will ich ja auch verstehen, was ich da tue :) Aber trotzdem vielen Dank.

Ich benutze Python 2.7.10, kein Python 3.
Antworten