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.
Sirius3
User
Beiträge: 17754
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: 17754
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.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich habe nochmals über die Problematik des Zusammenfassens der Konfigurationen nachgedacht und herausgekommen ist folgendes Programm. Dieses sollte nun soweit funktionieren, wie in meiner ersten Antwort aufgezeichnet. Darüber hinaus verwendet die get_configuration-Funktion nunmehr einen Dateiobjekt, so spielt es besser mit Sirius3s Beispiel zusammen:

Code: Alles auswählen

#!/usr/bin/env python3
import configparser
import itertools


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

            
def merge_dicts(a, b):  # -> dict
    """Merges two dicts prefering the values of the second one."""
    return {k: v for k, v in itertools.chain(a.items(), b.items())}

    
def merge_configurations(a, b):  # -> dict
    # 1. Dafür Sorge trage, dass das Ergebnis die Sektionen beider
    #    Konfigurationen enthält. Hierzu bilde ich zunächst ein set
    #    (=Menge) über die Sektionsnamen beider Konfigurationen.
    section_names = set(itertools.chain(a.keys(), b.keys()))
    
    # 2. Dictionary erzeugen und die Inhalte der Sektionen
    #    zusammenfassen (merge_dicts). Hierbei werden die Inhalte des
    #    zweiten Konfiguration bevorzugt.
    return {section_name: merge_dicts(a.get(section_name, {}),
                                      b.get(section_name, {}))
            for section_name
            in section_names}
    

def main():
    with open('default.ini') as default_config_file:
        default_configuration = get_configuration(default_config_file)
        
    with open('user.ini') as user_config_file:
        user_configuration = get_configuration(user_config_file)
        
    print(merge_configurations(default_configuration, user_configuration))

    
if __name__ == '__main__':
    main()
Hier bietet sich ein Blick in die Dokumentation zu itertools und zu LC/DC im Allgemeinen an.

PS: Ich nutze ausschließlich Python 3.5., configparser.ConfigParser.read_file im obigen Beispiel müsste ggf. durch readfp ersetzt werden. readfp ist ab Python 3.2 veraltet.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
BlackJack

@bwbg: Die „dict comprehension“ finde ich hier ungünstig. Wenn man mit den Schlüssel/Wert-Paaren nichts macht, kann man auch einfacher und kürzer einfach `dict()` verwenden: ``return dict(itertools.chain(a.items(), b.items()))``. Alternativ ginge auch noch ``result = dict(a); result.update(b); return result``.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Für die Funktion merge_dicts stimme ich Dir sogar zu ... 8)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

die einzeilige Variante wäre `result = dict(a, **b)`.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Damit ich hinterher komme, bedeuten diese beiden Sternchen (**), dass das Wörterbuch in einem Rutsch aktualisiert wird? Denn ich kenne diese Sternchen nur im Kontext von Funktionen (*args, **kwargs), um beliebe Anzahl von Argumenten oder Positionensargumente übergeben zu können.
BlackJack

@Sophus: Die ``**`` bedeuten hier genau das gleiche wie bei jedem anderen Funktions- oder Methodenaufruf.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Sirius3 hat geschrieben:die einzeilige Variante wäre `result = dict(a, **b)`.
Das Problem hier ist jedoch, dass eine (unvollständige) Sektion aus der user.ini die entsprechende aus der default.ini ersetzt. merge_configurations ließe sich in einem Ausdruck schreiben, aber nicht wirklich lesbar in einer Zeile :wink:

Sophus hat nun vom Baum der Erkenntnis gekostet: Jede neue Antwort bringt neue Fragen.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Sirius3 hat geschrieben:die einzeilige Variante wäre `result = dict(a, **b)`.
Hm.

Code: Alles auswählen

>>> a = {1: 2}
>>> b = {1: 3, 2: 3}
>>> c = {2: 4, 3: 4}
>>> dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
>>> {**a, **b}
{1: 3, 2: 3}
>>> {**a, **b, **c}
{1: 3, 2: 4, 3: 4}
Die tolle neue Syntax gibt es übrigens seit 3.5 und ist in PEP 448 beschrieben.
BlackJack

@DasIch: Bei der Konfiguration kann man allerdings davon ausgehen, dass die Schlüssel Zeichenketten sind. :-)
Antworten