Usar ConfigParser para leer un archivo sin nombre de sección

87

Estoy usando ConfigParserpara leer la configuración de tiempo de ejecución de un script.

Me gustaría tener la flexibilidad de no proporcionar un nombre de sección (hay scripts que son lo suficientemente simples; no necesitan una 'sección'). ConfigParserlanzará una NoSectionErrorexcepción y no aceptará el archivo.

¿Cómo puedo hacer que ConfigParser simplemente recupere las (key, value)tuplas de un archivo de configuración sin nombres de sección?

Por ejemplo:

key1=val1
key2:val2

Preferiría no escribir en el archivo de configuración.

Escualo
fuente
Posible duplicado del archivo
Håken Lid

Respuestas:

52

Alex Martelli proporcionó una solución para ConfigParseranalizar .propertiesarchivos (que aparentemente son archivos de configuración sin sección).

Su solución es un contenedor similar a un archivo que insertará automáticamente un encabezado de sección ficticia para satisfacer ConfigParserlos requisitos.

Will McCutchen
fuente
+1 porque eso es exactamente lo que estaba a punto de sugerir. ¿Por qué agregar toda la complejidad cuando todo lo que tiene que hacer es simplemente agregar una sección?
jathanismo
5
@jathanism: hay casos en los que desea trabajar con archivos de configuración / propiedades existentes, que son leídos por el código Java existente y no conoce el riesgo de modificar esos encabezados
tshepang
42

Iluminado por esta respuesta de jterrace , se me ocurre esta solución:

  1. Leer el archivo completo en una cadena
  2. Prefijo con un nombre de sección predeterminado
  3. Use StringIO para imitar un objeto similar a un archivo
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


EDITAR para futuros usuarios de Google: a partir de Python 3.4+ readfpestá obsoleto y StringIOya no es necesario. En su lugar, podemos usar read_stringdirectamente:

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

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)
xmo
fuente
¡Esto también funciona de maravilla para analizar un Makefile simple (con solo alias)! Aquí hay un script completo para sustituir los alias por sus comandos completos en Python , inspirado en esta respuesta.
gaborous
41

Puede hacer esto en una sola línea de código.

En Python 3, anteponga un encabezado de sección falso a los datos de su archivo de configuración y páselo 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.

También puede usar itertools.chain()para simular un encabezado de sección para read_file(). Esto podría ser más eficiente en memoria que el enfoque anterior, lo que podría ser útil si tiene archivos de configuración grandes en un entorno de ejecución restringido.

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)

En Python 2, anteponga un encabezado de sección falso a los datos de su archivo de configuración, envuelva el resultado en un StringIOobjeto y páselo 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 cualquiera de estos enfoques, su configuración de configuración estará disponible en parser.items('top').

También podría usar StringIO en Python 3, quizás por compatibilidad con los intérpretes de Python nuevos y antiguos, pero tenga en cuenta que ahora vive en el iopaquete y readfp()ahora está en desuso.

Alternativamente, podría considerar usar un analizador TOML en lugar de ConfigParser.

ʇsәɹoɈ
fuente
18

Puede usar la biblioteca ConfigObj para hacerlo simplemente: http://www.voidspace.org.uk/python/configobj.html

Actualizado: encuentre el código más reciente aquí .

Si está en Debian / Ubuntu, puede instalar este módulo usando su administrador de paquetes:

apt-get install python-configobj

Un ejemplo 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
fuente
8

La forma más fácil de hacer esto es usar el analizador CSV de Python, en mi opinión. Aquí hay una función de lectura / escritura que demuestra este enfoque, así como un controlador de prueba. Esto debería funcionar siempre que no se permita que los valores sean de varias líneas. :)

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
fuente
+1 Uso inteligente del csvmódulo para resolver ConfigParserquejas comunes . Fácilmente generalizado y hecho para ser compatible con Python 2 y 3 .
martineau
6

Habiéndome encontrado con este problema, escribí un contenedor completo para ConfigParser (la versión en Python 2) que puede leer y escribir archivos sin secciones de forma transparente, según el enfoque de Alex Martelli vinculado a la respuesta aceptada. Debería ser un reemplazo directo de cualquier uso de ConfigParser. Publicarlo en caso de que alguien que lo necesite encuentre 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
fuente
5

La respuesta de Blueicefield mencionó configobj, pero la biblioteca original solo es compatible con Python 2. Ahora tiene un puerto compatible con Python 3+:

https://github.com/DiffSK/configobj

Las API no han cambiado, consulte su documento .

laike9m
fuente