Utilizzo di ConfigParser per leggere un file senza il nome della sezione


87

Sto usando ConfigParserper leggere la configurazione di runtime di uno script.

Vorrei avere la flessibilità di non fornire un nome di sezione (ci sono script che sono abbastanza semplici; non hanno bisogno di una 'sezione'). ConfigParsergenererà NoSectionErrorun'eccezione e non accetterà il file.

Come posso fare in modo che ConfigParser recuperi semplicemente le (key, value)tuple di un file di configurazione senza i nomi delle sezioni?

Per esempio:

key1=val1
key2:val2

Preferirei non scrivere nel file di configurazione.


Possibile duplicato del file
Håken Lid

Risposte:


52

Alex Martelli ha fornito una soluzione da utilizzare ConfigParserper analizzare i .propertiesfile (che apparentemente sono file di configurazione senza sezione).

La sua soluzione è un wrapper simile a un file che inserirà automaticamente un'intestazione di sezione fittizia per soddisfare ConfigParseri requisiti di.


+1 perché è esattamente quello che stavo per suggerire. Perché aggiungere tutta la complessità quando tutto ciò che devi fare è solo aggiungere una sezione!
jathanism

5
@ jathanism: ci sono casi in cui desideri lavorare con file di configurazione / proprietà esistenti, che vengono letti dal codice Java esistente e non conosci il rischio di modificare quelle intestazioni
tshepang

42

Illuminato da questa risposta di jterrace , ho trovato questa soluzione:

  1. Legge l'intero file in una stringa
  2. Prefisso con un nome di sezione predefinito
  3. Usa StringIO per imitare un oggetto simile a un file
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


MODIFICA per i futuri googler: a partire da Python 3.4+ readfpè deprecato e StringIOnon è più necessario. Invece possiamo usare read_stringdirettamente:

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

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)

Questo fa miracoli anche per analizzare un semplice Makefile (con solo alias)! Ecco uno script completo per sostituire gli alias con i loro comandi completi in Python , ispirato a questa risposta.
gaborous

41

Puoi farlo in una singola riga di codice.

In python 3, anteponi un'intestazione di sezione falsa ai dati del tuo file di configurazione e passala a 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.

Puoi anche usare itertools.chain()per simulare un'intestazione di sezione per read_file(). Ciò potrebbe essere più efficiente in termini di memoria rispetto all'approccio precedente, il che potrebbe essere utile se si hanno file di configurazione di grandi dimensioni in un ambiente di runtime limitato.

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)

In python 2, anteponi un'intestazione di sezione falsa ai dati del tuo file di configurazione, racchiudi il risultato in un StringIOoggetto e passalo a 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)

Con uno qualsiasi di questi approcci, le tue impostazioni di configurazione saranno disponibili in parser.items('top').

Potresti usare StringIO anche in python 3, forse per compatibilità con interpreti python sia vecchi che nuovi, ma nota che ora vive nel iopacchetto ed readfp()è ora deprecato.

In alternativa, potresti prendere in considerazione l'utilizzo di un parser TOML invece di ConfigParser.


18

Puoi usare la libreria ConfigObj per farlo semplicemente: http://www.voidspace.org.uk/python/configobj.html

Aggiornato: trova il codice più recente qui .

Se sei sotto Debian / Ubuntu, puoi installare questo modulo usando il tuo gestore di pacchetti:

apt-get install python-configobj

Un esempio di utilizzo:

from configobj import ConfigObj

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

8

Il modo più semplice per farlo è usare il parser CSV di Python, secondo me. Ecco una funzione di lettura / scrittura che dimostra questo approccio e un test driver. Questo dovrebbe funzionare a condizione che i valori non possano essere multilinea. :)

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() 

+1 Uso intelligente del csvmodulo per risolvere i ConfigParserreclami comuni . Facilmente generalizzato di più e realizzato per essere compatibile con Python 2 e 3 .
martineau

6

Avendo riscontrato questo problema io stesso, ho scritto un wrapper completo per ConfigParser (la versione in Python 2) che può leggere e scrivere file senza sezioni in modo trasparente, basato sull'approccio di Alex Martelli collegato alla risposta accettata. Dovrebbe essere una sostituzione immediata di qualsiasi utilizzo di ConfigParser. Pubblicandolo nel caso in cui qualcuno ne avesse bisogno trova questa pagina.

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)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.