Verwenden von ConfigParser zum Lesen einer Datei ohne Abschnittsnamen

85

Ich verwende ConfigParser, um die Laufzeitkonfiguration eines Skripts zu lesen.

Ich möchte die Flexibilität haben, keinen Abschnittsnamen anzugeben (es gibt Skripte, die einfach genug sind; sie benötigen keinen 'Abschnitt'). ConfigParserlöst eine NoSectionErrorAusnahme aus und akzeptiert die Datei nicht.

Wie kann ich ConfigParser dazu bringen, einfach die (key, value)Tupel einer Konfigurationsdatei ohne Abschnittsnamen abzurufen ?

Zum Beispiel:

key1=val1
key2:val2

Ich würde lieber nicht in die Konfigurationsdatei schreiben.

Escualo
quelle
Mögliches Duplikat der Analyse der .properties-Datei in Python
Håken Lid

Antworten:

51

Alex Martelli bot eine Lösung für ConfigParserdas Parsen von .propertiesDateien (bei denen es sich anscheinend um abschnittslose Konfigurationsdateien handelt).

Seine Lösung ist ein dateiähnlicher Wrapper, der automatisch eine Dummy-Abschnittsüberschrift einfügt, um ConfigParserdie Anforderungen zu erfüllen .

Will McCutchen
quelle
+1, weil ich genau das vorschlagen wollte. Warum die ganze Komplexität hinzufügen, wenn Sie nur einen Abschnitt hinzufügen müssen?
Nathanismus
5
@ Nathanism: Es gibt Fälle, in denen Sie mit vorhandenen Konfigurations- / Eigenschaftendateien arbeiten möchten, die von vorhandenem Java-Code gelesen werden, und Sie kennen das Risiko der Änderung dieser Header nicht
Tshepang
42

Erleuchtet von dieser Antwort von jterrace , habe ich folgende Lösung gefunden:

  1. Lesen Sie die gesamte Datei in eine Zeichenfolge
  2. Präfix mit einem Standardabschnittsnamen
  3. Verwenden Sie StringIO, um ein dateiähnliches Objekt nachzuahmen
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


EDIT für zukünftige Googler: Ab Python 3.4+ readfpist veraltet und StringIOwird nicht mehr benötigt. Stattdessen können wir read_stringdirekt verwenden:

with open('config_file') as f:
    file_content = '[dummy_section]\n' + f.read()

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)
xmo
quelle
Dies wirkt auch Wunder, um ein einfaches Makefile (nur mit Aliasnamen) zu analysieren! Hier ist ein vollständiges Skript, um Aliase durch ihre vollständigen Befehle in Python zu ersetzen , inspiriert von dieser Antwort.
gaborous
39

Sie können dies in einer einzigen Codezeile tun.

Stellen Sie in Python 3 Ihren Konfigurationsdateidaten einen gefälschten Abschnittsheader voran und übergeben Sie ihn an read_string().

from configparser import ConfigParser

parser = ConfigParser()
with open("foo.conf") as stream:
    parser.read_string("[top]\n" + stream.read())  # This line does the trick.

Sie können auch itertools.chain()eine Abschnittsüberschrift für simulieren read_file(). Dies ist möglicherweise speichereffizienter als der oben beschriebene Ansatz. Dies kann hilfreich sein, wenn Sie große Konfigurationsdateien in einer eingeschränkten Laufzeitumgebung haben.

from configparser import ConfigParser
from itertools import chain

parser = ConfigParser()
with open("foo.conf") as lines:
    lines = chain(("[top]",), lines)  # This line does the trick.
    parser.read_file(lines)

Stellen Sie in Python 2 Ihren Konfigurationsdateidaten einen gefälschten Abschnittsheader voran, wickeln Sie das Ergebnis in ein StringIOObjekt ein und übergeben Sie es an readfp().

from ConfigParser import ConfigParser
from StringIO import StringIO

parser = ConfigParser()
with open("foo.conf") as stream:
    stream = StringIO("[top]\n" + stream.read())  # This line does the trick.
    parser.readfp(stream)

Bei jedem dieser Ansätze sind Ihre Konfigurationseinstellungen in verfügbar parser.items('top').

Sie können StringIO auch in Python 3 verwenden, möglicherweise aus Gründen der Kompatibilität mit alten und neuen Python-Interpreten. Beachten Sie jedoch, dass es jetzt im ioPaket enthalten ist und readfp()jetzt veraltet ist.

Alternativ können Sie einen TOML- Parser anstelle von ConfigParser verwenden.

ʇSәɹoɈ
quelle
18

Sie können die ConfigObj-Bibliothek verwenden, um dies einfach zu tun: http://www.voidspace.org.uk/python/configobj.html

Aktualisiert: Den neuesten Code finden Sie hier .

Wenn Sie unter Debian / Ubuntu arbeiten, können Sie dieses Modul mit Ihrem Paketmanager installieren:

apt-get install python-configobj

Ein Anwendungsbeispiel:

from configobj import ConfigObj

config = ConfigObj('myConfigFile.ini')
config.get('key1') # You will get val1
config.get('key2') # You will get val2
Blueicefield
quelle
8

Der einfachste Weg, dies zu tun, ist meiner Meinung nach die Verwendung des CSV-Parsers von Python. Hier ist eine Lese- / Schreibfunktion, die diesen Ansatz demonstriert, sowie ein Testtreiber. Dies sollte funktionieren, sofern die Werte nicht mehrzeilig sein dürfen. :) :)

import csv
import operator

def read_properties(filename):
    """ Reads a given properties file with each line of the format key=value.  Returns a dictionary containing the pairs.

    Keyword arguments:
        filename -- the name of the file to be read
    """
    result={ }
    with open(filename, "rb") as csvfile:
        reader = csv.reader(csvfile, delimiter='=', escapechar='\\', quoting=csv.QUOTE_NONE)
        for row in reader:
            if len(row) != 2:
                raise csv.Error("Too many fields on row with contents: "+str(row))
            result[row[0]] = row[1] 
    return result

def write_properties(filename,dictionary):
    """ Writes the provided dictionary in key-sorted order to a properties file with each line of the format key=value

    Keyword arguments:
        filename -- the name of the file to be written
        dictionary -- a dictionary containing the key/value pairs.
    """
    with open(filename, "wb") as csvfile:
        writer = csv.writer(csvfile, delimiter='=', escapechar='\\', quoting=csv.QUOTE_NONE)
        for key, value in sorted(dictionary.items(), key=operator.itemgetter(0)):
                writer.writerow([ key, value])

def main():
    data={
        "Hello": "5+5=10",
        "World": "Snausage",
        "Awesome": "Possum"
    }

    filename="test.properties"
    write_properties(filename,data)
    newdata=read_properties(filename)

    print "Read in: "
    print newdata
    print

    contents=""
    with open(filename, 'rb') as propfile:
        contents=propfile.read()
    print "File contents:"
    print contents

    print ["Failure!", "Success!"][data == newdata]
    return

if __name__ == '__main__': 
     main() 
Nacitar Sevaht
quelle
+1 Clevere Verwendung des csvModuls zur Lösung häufiger ConfigParserBeschwerden. Einfacher verallgemeinert und sowohl Python 2 als auch 3-kompatibel gemacht .
Martineau
6

Nachdem ich selbst auf dieses Problem gestoßen war, schrieb ich einen vollständigen Wrapper an ConfigParser (die Version in Python 2), der Dateien ohne Abschnitte transparent lesen und schreiben kann, basierend auf Alex Martellis Ansatz, der mit der akzeptierten Antwort verknüpft ist. Es sollte ein Ersatz für jede Verwendung von ConfigParser sein. Veröffentlichen Sie es, falls jemand, der dies benötigt, diese Seite findet.

import ConfigParser
import StringIO

class SectionlessConfigParser(ConfigParser.RawConfigParser):
    """
    Extends ConfigParser to allow files without sections.

    This is done by wrapping read files and prepending them with a placeholder
    section, which defaults to '__config__'
    """

    def __init__(self, *args, **kwargs):
        default_section = kwargs.pop('default_section', None)
        ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)

        self._default_section = None
        self.set_default_section(default_section or '__config__')

    def get_default_section(self):
        return self._default_section

    def set_default_section(self, section):
        self.add_section(section)

        # move all values from the previous default section to the new one
        try:
            default_section_items = self.items(self._default_section)
            self.remove_section(self._default_section)
        except ConfigParser.NoSectionError:
            pass
        else:
            for (key, value) in default_section_items:
                self.set(section, key, value)

        self._default_section = section

    def read(self, filenames):
        if isinstance(filenames, basestring):
            filenames = [filenames]

        read_ok = []
        for filename in filenames:
            try:
                with open(filename) as fp:
                    self.readfp(fp)
            except IOError:
                continue
            else:
                read_ok.append(filename)

        return read_ok

    def readfp(self, fp, *args, **kwargs):
        stream = StringIO()

        try:
            stream.name = fp.name
        except AttributeError:
            pass

        stream.write('[' + self._default_section + ']\n')
        stream.write(fp.read())
        stream.seek(0, 0)

        return ConfigParser.RawConfigParser.readfp(self, stream, *args,
                                                   **kwargs)

    def write(self, fp):
        # Write the items from the default section manually and then remove them
        # from the data. They'll be re-added later.
        try:
            default_section_items = self.items(self._default_section)
            self.remove_section(self._default_section)

            for (key, value) in default_section_items:
                fp.write("{0} = {1}\n".format(key, value))

            fp.write("\n")
        except ConfigParser.NoSectionError:
            pass

        ConfigParser.RawConfigParser.write(self, fp)

        self.add_section(self._default_section)
        for (key, value) in default_section_items:
            self.set(self._default_section, key, value)
danielkza
quelle
5

In der Antwort von Blueicefield wurde configobj erwähnt, aber die ursprüngliche Bibliothek unterstützt nur Python 2. Sie verfügt jetzt über einen Python 3+ -kompatiblen Port:

https://github.com/DiffSK/configobj

APIs haben sich nicht geändert, siehe Dokument .

laike9m
quelle