Używanie ConfigParser do odczytu pliku bez nazwy sekcji

87

Używam ConfigParserdo odczytywania konfiguracji skryptu w czasie wykonywania.

Chciałbym mieć elastyczność polegającą na nie podawaniu nazwy sekcji (istnieją skrypty, które są dość proste; nie potrzebują „sekcji”). ConfigParserzgłosi NoSectionErrorwyjątek i nie zaakceptuje pliku.

Jak sprawić, by ConfigParser po prostu pobierał (key, value)krotki z pliku konfiguracyjnego bez nazw sekcji?

Na przykład:

key1=val1
key2:val2

Wolałbym nie pisać do pliku konfiguracyjnego.

Escualo
źródło
Możliwy duplikat parsowania pliku .properties w Pythonie
Håken Lid

Odpowiedzi:

52

Alex Martelli pod warunkiem rozwiązania za korzystanie ConfigParseranalizować .propertiesplików (które są najwyraźniej sekcja mniej pliki config).

Jego rozwiązaniem jest opakowanie przypominające plik, które automagicznie wstawia fikcyjny nagłówek sekcji, aby spełnić ConfigParserwymagania.

Will McCutchen
źródło
+1, ponieważ właśnie to chciałem zasugerować. Po co dodawać całą złożoność, skoro wszystko, co musisz zrobić, to po prostu dodać sekcję!
jatanizm
5
@jathanism: są przypadki, w których chcesz pracować z istniejącymi plikami konfiguracyjnymi / właściwościami, które są odczytywane przez istniejący kod Java i nie znasz ryzyka modyfikacji tych nagłówków
tshepang
43

Oświecony tą odpowiedzią jterrace , wymyślam takie rozwiązanie:

  1. Wczytaj cały plik do ciągu
  2. Przedrostek z domyślną nazwą sekcji
  3. Użyj StringIO, aby naśladować obiekt podobny do pliku
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


EDYCJA dla przyszłych pracowników Google: od wersji Python 3.4+ readfpjest przestarzała i StringIOnie jest już potrzebna. Zamiast tego możemy użyć read_stringbezpośrednio:

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

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)
xmo
źródło
Działa to również cuda przy parsowaniu prostego pliku Makefile (tylko z aliasami)! Oto pełny skrypt zastępujący aliasy ich pełnymi poleceniami w Pythonie , zainspirowany tą odpowiedzią.
hałaśliwy
41

Możesz to zrobić w jednej linii kodu.

W Pythonie 3 dołącz fałszywy nagłówek sekcji do danych pliku konfiguracyjnego i przekaż go do 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.

Możesz również użyć itertools.chain()do symulacji nagłówka sekcji dla read_file(). Może to być bardziej wydajne pod względem wykorzystania pamięci niż powyższe podejście, co może być przydatne, jeśli masz duże pliki konfiguracyjne w ograniczonym środowisku wykonawczym.

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)

W Pythonie 2 dołącz fałszywy nagłówek sekcji do danych pliku konfiguracyjnego, zawiń wynik w StringIOobiekt i przekaż go do 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)

Przy każdym z tych podejść ustawienia konfiguracji będą dostępne w parser.items('top') .

Możesz użyć StringIO również w Pythonie 3, być może ze względu na kompatybilność zarówno ze starymi, jak i nowymi interpreterami Pythona, ale pamiętaj, że teraz znajduje się w iopakiecie i readfp()jest przestarzały.

Alternatywnie możesz rozważyć użycie parsera TOML zamiast ConfigParser.

ʇsәɹoɈ
źródło
18

Możesz to zrobić za pomocą biblioteki ConfigObj: http://www.voidspace.org.uk/python/configobj.html

Zaktualizowano: znajdź najnowszy kod tutaj .

Jeśli korzystasz z Debiana / Ubuntu, możesz zainstalować ten moduł za pomocą swojego menedżera pakietów:

apt-get install python-configobj

Przykładowe zastosowanie:

from configobj import ConfigObj

config = ConfigObj('myConfigFile.ini')
config.get('key1') # You will get val1
config.get('key2') # You will get val2
Blueicefield
źródło
8

Moim zdaniem najłatwiejszym sposobem jest użycie parsera CSV Pythona. Oto funkcja odczytu / zapisu demonstrująca to podejście, a także sterownik testowy. To powinno działać, pod warunkiem, że wartości nie mogą być wieloliniowe. :)

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
źródło
+1 Sprytne wykorzystanie csvmodułu do rozwiązywania typowych ConfigParserskarg. Łatwo uogólnione i bardziej zgodne z Pythonem 2 i 3 .
martineau
6

Sam napotkałem ten problem i napisałem kompletny wrapper do ConfigParser (wersja w Pythonie 2), który może odczytywać i zapisywać pliki bez sekcji w sposób przejrzysty, w oparciu o podejście Alexa Martelliego połączone z zaakceptowaną odpowiedzią. Powinien zastępować dowolne użycie narzędzia ConfigParser. Publikowanie go na wypadek, gdyby ktoś potrzebujący znalazł tę stronę.

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
źródło