Listas en ConfigParser

Respuestas:

142

No hay nada que le impida empaquetar la lista en una cadena delimitada y luego desempaquetarla una vez que obtenga la cadena de la configuración. Si lo hiciera de esta manera, su sección de configuración se vería así:

[Section 3]
barList=item1,item2

No es bonito, pero es funcional para la mayoría de las listas simples.

David Locke
fuente
2
Y si tiene listas complejas, puede consultar esta pregunta: stackoverflow.com/questions/330900/… :-)
John Fouhy
buena solución, pero ¿cómo hacerlo si no hay un delimitador posible que pueda garantizar que no aparecerá dentro de un elemento de la lista?
wim
@wim Mira mi respuesta, puedes usar \ n como delimitador
Peter Smit
@wim Debería implementar una forma de escapar del carácter delimitador si puede ser un carácter legal. (Y una forma de escapar de cualquier personaje que uses para escapar.)
jamesdlin
¿Qué pasa si una lista tiene un solo elemento?
Sérgio Mafra el
223

También un poco tarde, pero tal vez útil para algunos. Estoy usando una combinación de ConfigParser y JSON:

[Foo]
fibs: [1,1,2,3,5,8,13]

solo léelo con:

>>> json.loads(config.get("Foo","fibs"))
[1, 1, 2, 3, 5, 8, 13]

Incluso puede romper líneas si su lista es larga (gracias @ peter-smit):

[Bar]
files_to_check = [
     "/path/to/file1",
     "/path/to/file2",
     "/path/to/another file with space in the name"
     ]

Por supuesto, podría usar JSON, pero los archivos de configuración me parecen mucho más legibles, y la sección [POR DEFECTO] es muy útil.

cuasimodo
fuente
1
Es impresionante porque automáticamente "arroja" valores que pueden ser útiles si no conoce los tipos de antemano.
LeGBT
Me encanta esta idea, pero solo puedo lograr que funcione con listas de números. Las comillas no ayudan. Extraño. Hacia adelante.
rsaw
55
Deberá tener ["a", "b", "c"] para que las cadenas funcionen. Para mí, esto hace clic para los números, pero como los archivos cfg son en su mayoría editables, agregar "" cada vez es una molestia. Prefiero usar una coma y luego dividirla.
Saurabh Hirani
Una solución elegante usando solo la biblioteca estándar. Es bueno poder usar comentarios y json.
wi1
¿Cómo funcionaría esto para cadenas sin procesar, por ejemplo key5 : [r"abc $x_i$", r"def $y_j$"]? Ellos plantean el errorjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu
101

Llegué tarde a esta fiesta, pero recientemente implementé esto con una sección dedicada en un archivo de configuración para una lista:

[paths]
path1           = /some/path/
path2           = /another/path/
...

y usar config.items( "paths" )para obtener una lista iterable de elementos de ruta, así:

path_items = config.items( "paths" )
for key, path in path_items:
    #do something with path

Espero que esto ayude a otras personas a buscar en Google esta pregunta;)

Henry Cooke
fuente
3
Me gusta esta solución, porque puedes ; commenteliminar ciertos elementos de la lista sin tener que volver a escribir la lista completa.
wim
1
+1, pero si haces esto, solo ten cuidado con el uso key, ya que ConfigParser convierte todas esas claves en minúsculas
Alex Dean
44
@AlexDean Puede configurar ConfigParser para dejar CamelCase en su lugar configurando optionxform = str. Ejemplo: config = ConfigParser.SafeConfigParser() config.optionxform = str Entonces el caso se quedará solo
Cameron Goodale
@Henry Cooke ¿Has probado eso cuando una clave aparece varias veces?
DevPlayer
1
@DevPlayer Con el uso de múltiples claves solo obtienes el último valor. (respondiendo al comentario de 2 años para beneficio de otros lectores)
Marcin K
63

Una cosa que mucha gente no sabe es que se permiten valores de configuración de varias líneas. Por ejemplo:

;test.ini
[hello]
barlist = 
    item1
    item2

El valor de config.get('hello','barlist')ahora será:

"\nitem1\nitem2"

Que puedes dividir fácilmente con el método de líneas divisorias (no olvides filtrar los elementos vacíos).

Si observamos un gran marco como Pyramid, están utilizando esta técnica:

def aslist_cronly(value):
    if isinstance(value, string_types):
        value = filter(None, [x.strip() for x in value.splitlines()])
    return list(value)

def aslist(value, flatten=True):
    """ Return a list of strings, separating the input based on newlines
    and, if flatten=True (the default), also split on spaces within
    each line."""
    values = aslist_cronly(value)
    if not flatten:
        return values
    result = []
    for value in values:
        subvalues = value.split()
        result.extend(subvalues)
    return result

Fuente

Yo mismo, tal vez extienda el ConfigParser si esto es algo común para usted:

class MyConfigParser(ConfigParser):
    def getlist(self,section,option):
        value = self.get(section,option)
        return list(filter(None, (x.strip() for x in value.splitlines())))

    def getlistint(self,section,option):
        return [int(x) for x in self.getlist(section,option)]

Tenga en cuenta que hay algunas cosas a tener en cuenta al usar esta técnica

  1. Las nuevas líneas que son elementos deben comenzar con espacios en blanco (por ejemplo, un espacio o una pestaña)
  2. Todas las siguientes líneas que comienzan con espacios en blanco se consideran parte del elemento anterior. Además, si tiene un signo = o si comienza con a; siguiendo el espacio en blanco.
Peter Smit
fuente
¿Por qué usas en .splitlines()lugar de .split()? Usando el comportamiento predeterminado de cada uno, la división es claramente superior (filtra las líneas en blanco). A menos que me falta algo ...
rsaw
77
.split () se rompe en todos los espacios en blanco (a menos que se proporcione un carácter específico), .splitlines () se rompe en todos los caracteres de nueva línea.
Peter Smit el
Ahhh buen punto. No pensé en eso ya que ninguno de mis valores tenía espacios.
rsaw
38

Si quiere pasar literalmente una lista, puede usar:

ast.literal_eval()

Por ejemplo, configuración:

[section]
option=["item1","item2","item3"]

El codigo es:

import ConfigParser
import ast

my_list = ast.literal_eval(config.get("section", "option"))
print(type(my_list))
print(my_list)

salida:

<type'list'>
["item1","item2","item3"]
PythonTester
fuente
En este caso, ¿cuál es la ventaja de usar ast.literal_eval()cuando se compara con el uso (posiblemente más popular) json.loads()? Creo que este último proporciona más seguridad, ¿no?
RayLuo
2
Me encantaría ver un ejemplo de esto, siéntase libre de agregar una respuesta a este hilo si cree que ayudaría, aunque su comentario sería una buena pregunta en sí mismo. La respuesta que di simplifica el consumo de listas de ConfigParser, por lo que es interna a la aplicación, eliminando la complicación de usar regex. No podría comentar sobre su valor de "seguridad" sin contexto.
PythonTester
Tendría cuidado al usar literal_eval que espera una cadena de Python después de = o: por lo tanto, no puede usar más, por ejemplo, path1 = / some / path / but path1 = '/ some / path /'
vldbnc
21

No se menciona el converterskwarg paraConfigParser() en ninguna de estas respuestas fue bastante decepcionante.

De acuerdo con la documentación, puede pasar un diccionario al ConfigParserque agregará unget método tanto para el analizador como para los proxies de sección. Entonces para una lista:

ejemplo.ini

[Germ]
germs: a,list,of,names, and,1,2, 3,numbers

Ejemplo de analizador:

cp = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]})
cp.read('example.ini')
cp.getlist('Germ', 'germs')
['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers']
cp['Germ'].getlist('germs')
['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers']

Este es mi favorito personal ya que no es necesaria una subclasificación y no tengo que depender de un usuario final para escribir perfectamente JSON o una lista que pueda ser interpretada ast.literal_eval.

Grr
fuente
15

Aterricé aquí buscando consumir esto ...

[global]
spys = richard.sorge@cccp.gov, mata.hari@deutschland.gov

La respuesta es dividirlo en la coma y quitar los espacios:

SPYS = [e.strip() for e in parser.get('global', 'spys').split(',')]

Para obtener un resultado de la lista:

['[email protected]', '[email protected]']

Es posible que no responda exactamente la pregunta del OP, pero podría ser la respuesta simple que algunas personas están buscando.

John Mee
fuente
2
¡Pensé que Dick estaba en [email protected]! ¡No es de extrañar que mi correo siguiera rebotando! > _ <
Augusta
1
Leyendo este comentario 4 años después y riéndose del huevo de pascua
un ingeniero curioso el
11

Esto es lo que uso para las listas:

contenido del archivo de configuración:

[sect]
alist = a
        b
        c

codigo:

l = config.get('sect', 'alist').split('\n')

funciona para cuerdas

en caso de números

contenido de configuración:

nlist = 1
        2
        3

código:

nl = config.get('sect', 'alist').split('\n')
l = [int(nl) for x in nl]

Gracias.

LittleEaster
fuente
Este es el que realmente estaba buscando gracias @LittleEaster
ashley
5

Entonces, otra forma, que prefiero, es dividir los valores, por ejemplo:

#/path/to/config.cfg
[Numbers]
first_row = 1,2,4,8,12,24,36,48

Podría cargarse así en una lista de cadenas o enteros, de la siguiente manera:

import configparser

config = configparser.ConfigParser()
config.read('/path/to/config.cfg')

# Load into a list of strings
first_row_strings = config.get('Numbers', 'first_row').split(',')

# Load into a list of integers
first_row_integers = [int(x) for x in config.get('Numbers', 'first_row').split(',')]

Este método evita que necesite ajustar sus valores entre paréntesis para cargar como JSON.

Mitch Gates
fuente
Hola Mitch, en el último caso, ¿no habría sido más agradable usar get_int ('first_row'). Split (',') en lugar de convertirlo explícitamente a int durante el bucle?
Guido
2

Solo los tipos primitivos son compatibles para la serialización mediante el analizador de configuración. Usaría JSON o YAML para ese tipo de requisitos.

M. Utku ALTINKAYA
fuente
gracias por la aclaración, utku. El único problema es que no puedo usar paquetes externos en este momento. Creo que voy a escribir una clase simple para manejar esto. Lo compartiré eventualmente.
pistacho el
¿Qué versión de Python estás ejecutando? El módulo JSON se incluye con 2.6.
Patrick Harrington
2

Me enfrenté al mismo problema en el pasado. Si necesita listas más complejas, considere crear su propio analizador heredando de ConfigParser. Entonces sobrescribiría el método get con eso:

    def get(self, section, option):
    """ Get a parameter
    if the returning value is a list, convert string value to a python list"""
    value = SafeConfigParser.get(self, section, option)
    if (value[0] == "[") and (value[-1] == "]"):
        return eval(value)
    else:
        return value

Con esta solución también podrá definir diccionarios en su archivo de configuración.

¡Pero ten cuidado! Esto no es tan seguro: esto significa que cualquiera podría ejecutar código a través de su archivo de configuración. Si la seguridad no es un problema en su proyecto, consideraría usar directamente clases de Python como archivos de configuración. Lo siguiente es mucho más poderoso y prescindible que un archivo ConfigParser:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]
Mapad
fuente
Sin embargo, estaba pensando en hacer esto: ¿por qué no configurar los valores de configuración como barList=item1,item2y luego llamar if value.find(',') > 0: return value.split(','), o mejor aún, hacer que la aplicación analice todas las opciones de configuración como listas y solo .split(',')todo a ciegas?
Droogans
1
import ConfigParser
import os

class Parser(object):
    """attributes may need additional manipulation"""
    def __init__(self, section):
        """section to retun all options on, formatted as an object
        transforms all comma-delimited options to lists
        comma-delimited lists with colons are transformed to dicts
        dicts will have values expressed as lists, no matter the length
        """
        c = ConfigParser.RawConfigParser()
        c.read(os.path.join(os.path.dirname(__file__), 'config.cfg'))

        self.section_name = section

        self.__dict__.update({k:v for k, v in c.items(section)})

        #transform all ',' into lists, all ':' into dicts
        for key, value in self.__dict__.items():
            if value.find(':') > 0:
                #dict
                vals = value.split(',')
                dicts = [{k:v} for k, v in [d.split(':') for d in vals]]
                merged = {}
                for d in dicts:
                    for k, v in d.items():
                        merged.setdefault(k, []).append(v)
                self.__dict__[key] = merged
            elif value.find(',') > 0:
                #list
                self.__dict__[key] = value.split(',')

Así que ahora mi config.cfgarchivo, que podría verse así:

[server]
credentials=username:admin,password:$3<r3t
loggingdirs=/tmp/logs,~/logs,/var/lib/www/logs
timeoutwait=15

Se puede analizar en objetos suficientemente finos para mi pequeño proyecto.

>>> import config
>>> my_server = config.Parser('server')
>>> my_server.credentials
{'username': ['admin'], 'password', ['$3<r3t']}
>>> my_server.loggingdirs:
['/tmp/logs', '~/logs', '/var/lib/www/logs']
>>> my_server.timeoutwait
'15'

Esto es para un análisis muy rápido de configuraciones simples, pierde toda la capacidad de obtener entradas, bools y otros tipos de salida sin transformar el objeto devuelto Parsero volver a hacer el trabajo de análisis realizado por la clase Parser en otro lugar.

Droogans
fuente
1

Completé una tarea similar en mi proyecto con una sección con claves sin valores:

import configparser

# allow_no_value param says that no value keys are ok
config = configparser.ConfigParser(allow_no_value=True)

# overwrite optionxform method for overriding default behaviour (I didn't want lowercased keys)
config.optionxform = lambda optionstr: optionstr

config.read('./app.config')

features = list(config['FEATURES'].keys())

print(features)

Salida:

['BIOtag', 'TextPosition', 'IsNoun', 'IsNomn']

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
más débil
fuente
0

json.loads Y ast.literal_eval parece estar funcionando, pero una lista simple dentro de la configuración trata a cada carácter como un byte, por lo que devuelve incluso el corchete ...

es decir, si config tiene fieldvalue = [1,2,3,4,5]

luego config.read(*.cfg) config['fieldValue'][0]regresando [en lugar de1

Abhishek Jain
fuente
0

Según lo mencionado por Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ) Es posible que desee extender ConfigParser, además, se puede usar un interpolador para convertir automáticamente en y desde la lista.

Como referencia en la parte inferior, puede encontrar el código que convierte automáticamente la configuración como:

[DEFAULT]
keys = [
    Overall cost structure, Capacity, RAW MATERIALS,
    BY-PRODUCT CREDITS, UTILITIES, PLANT GATE COST,
    PROCESS DESCRIPTION, AT 50% CAPACITY, PRODUCTION COSTS,
    INVESTMENT, US$ MILLION, PRODUCTION COSTS, US ¢/LB,
    VARIABLE COSTS, PRODUCTION COSTS, MAINTENANCE MATERIALS
  ]

Entonces, si solicita claves, obtendrá:

<class 'list'>: ['Overall cost structure', 'Capacity', 'RAW MATERIALS', 'BY-PRODUCT CREDITS', 'UTILITIES', 'PLANT GATE COST', 'PROCESS DESCRIPTION', 'AT 50% CAPACITY', 'PRODUCTION COSTS', 'INVESTMENT', 'US$ MILLION', 'PRODUCTION COSTS', 'US ¢/LB', 'VARIABLE COSTS', 'PRODUCTION COSTS', 'MAINTENANCE MATERIALS']

Código:

class AdvancedInterpolator(Interpolation):
    def before_get(self, parser, section, option, value, defaults):
        is_list = re.search(parser.LIST_MATCHER, value)
        if is_list:
            return parser.getlist(section, option, raw=True)
        return value


class AdvancedConfigParser(ConfigParser):

    _DEFAULT_INTERPOLATION = AdvancedInterpolator()

    LIST_SPLITTER = '\s*,\s*'
    LIST_MATCHER = '^\[([\s\S]*)\]$'

    def _to_list(self, str):
        is_list = re.search(self.LIST_MATCHER, str)
        if is_list:
            return re.split(self.LIST_SPLITTER, is_list.group(1))
        else:
            return re.split(self.LIST_SPLITTER, str)


    def getlist(self, section, option, conv=lambda x:x.strip(), *, raw=False, vars=None,
                  fallback=_UNSET, **kwargs):
        return self._get_conv(
                section, option,
                lambda value: [conv(x) for x in self._to_list(value)],
                raw=raw,
                vars=vars,
                fallback=fallback,
                **kwargs
        )

    def getlistint(self, section, option, *, raw=False, vars=None,
            fallback=_UNSET, **kwargs):
        return self.getlist(section, option, int, raw=raw, vars=vars,
                fallback=fallback, **kwargs)

    def getlistfloat(self, section, option, *, raw=False, vars=None,
            fallback=_UNSET, **kwargs):
        return self.getlist(section, option, float, raw=raw, vars=vars,
                fallback=fallback, **kwargs)

    def getlistboolean(self, section, option, *, raw=False, vars=None,
            fallback=_UNSET, **kwargs):
        return self.getlist(section, option, self._convert_to_boolean,
                raw=raw, vars=vars, fallback=fallback, **kwargs)

Ps tener en cuenta la importancia de la sangría. Como se lee en la cadena de documentación de ConfigParser:

Los valores pueden abarcar varias líneas, siempre que tengan una sangría más profunda que la primera línea del valor. Dependiendo del modo del analizador, las líneas en blanco pueden tratarse como partes de valores de varias líneas o ignorarse.

Dominik Maszczyk
fuente