Usando o ConfigParser para ler um arquivo sem nome de seção

87

Estou usando ConfigParserpara ler a configuração de tempo de execução de um script.

Eu gostaria de ter a flexibilidade de não fornecer um nome de seção (existem scripts que são simples; eles não precisam de uma 'seção'). ConfigParserlançará uma NoSectionErrorexceção e não aceitará o arquivo.

Como posso fazer com que o ConfigParser simplesmente recupere as (key, value)tuplas de um arquivo de configuração sem nomes de seção?

Por exemplo:

key1=val1
key2:val2

Prefiro não gravar no arquivo de configuração.

Escualo
fonte
Possível duplicata do arquivo .properties
Håken Lid

Respostas:

52

Alex Martelli forneceu uma solução para usar ConfigParserpara analisar .propertiesarquivos (que aparentemente são arquivos de configuração sem seção).

Sua solução é um invólucro semelhante a um arquivo que irá inserir automagicamente um cabeçalho de seção fictício para satisfazer ConfigParseros requisitos de.

Will McCutchen
fonte
1 porque isso é exatamente o que eu estava prestes a sugerir. Por que adicionar toda a complexidade quando tudo o que você precisa fazer é apenas adicionar uma seção!
jathanismo de
5
@jathanism: há casos em que você deseja trabalhar com arquivos de configuração / propriedades existentes, que são lidos pelo código Java existente e você não sabe o risco de modificar esses cabeçalhos
tshepang
43

Iluminado por esta resposta de jterrace , eu vim com esta solução:

  1. Leia o arquivo inteiro em uma string
  2. Prefixo com um nome de seção padrão
  3. Use StringIO para imitar um objeto semelhante a um arquivo
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


EDIT para futuros googlers: a partir do Python 3.4+ readfpse tornou obsoleto e StringIOnão é mais necessário. Em vez disso, podemos usar read_stringdiretamente:

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

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)
xmo
fonte
Isso também faz maravilhas para analisar um Makefile simples (com apenas aliases)! Aqui está um script completo para substituir aliases por seus comandos completos em Python , inspirado por esta resposta.
gaborous
41

Você pode fazer isso em uma única linha de código.

No python 3, acrescente um cabeçalho de seção falso aos dados do arquivo de configuração e passe-o para 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.

Você também pode usar itertools.chain()para simular um cabeçalho de seção para read_file(). Isso pode ser mais eficiente em termos de memória do que a abordagem acima, o que pode ser útil se você tiver grandes arquivos de configuração em um ambiente de tempo de execução restrito.

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)

No python 2, acrescente um cabeçalho de seção falso aos dados do arquivo de configuração, envolva o resultado em um StringIOobjeto e passe-o para 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)

Com qualquer uma dessas abordagens, suas configurações estarão disponíveis em parser.items('top') .

Você também pode usar StringIO no python 3, talvez para compatibilidade com interpretadores python novos e antigos, mas observe que agora ele está no iopacote e readfp()está obsoleto.

Como alternativa, você pode considerar o uso de um analisador TOML em vez do ConfigParser.

ʇsәɹoɈ
fonte
18

Você pode usar a biblioteca ConfigObj para fazer isso simplesmente: http://www.voidspace.org.uk/python/configobj.html

Atualizado: Encontre o código mais recente aqui .

Se você estiver no Debian / Ubuntu, pode instalar este módulo usando seu gerenciador de pacotes:

apt-get install python-configobj

Um exemplo de uso:

from configobj import ConfigObj

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

A maneira mais fácil de fazer isso é usar o analisador CSV do python, na minha opinião. Aqui está uma função de leitura / gravação que demonstra essa abordagem, bem como um driver de teste. Isso deve funcionar, desde que os valores não possam ser multilinhas. :)

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
fonte
+1 Uso inteligente do csvmódulo para resolver ConfigParserreclamações comuns . Facilmente generalizado mais e feito para ser compatível com Python 2 e 3 .
martineau
6

Tendo enfrentado esse problema sozinho, escrevi um wrapper completo para ConfigParser (a versão em Python 2) que pode ler e gravar arquivos sem seções de forma transparente, com base na abordagem de Alex Martelli vinculada à resposta aceita. Deve ser um substituto imediato para qualquer uso do ConfigParser. Postar no caso de alguém que precise encontrar esta página.

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
fonte
5

A resposta de Blueicefield mencionou configobj, mas a lib original só oferece suporte a Python 2. Ela agora tem uma porta compatível com Python 3+:

https://github.com/DiffSK/configobj

APIs não mudaram, veja seu doc .

laike9m
fonte