Listas no ConfigParser

Respostas:

142

Não há nada que o impeça de empacotar a lista em uma sequência delimitada e descompactá-la depois de obter a sequência da configuração. Se você fez dessa maneira, sua seção de configuração se pareceria com:

[Section 3]
barList=item1,item2

Não é bonito, mas é funcional para a maioria das listas simples.

David Locke
fonte
2
E se você tem listas complexas, você pode se referir a esta pergunta: stackoverflow.com/questions/330900/... :-)
John Fouhy
solução agradável, mas como fazê-lo se não houver delimitador possível do que você pode garantir, não aparecerá dentro de um item da lista ???
Wim
@wim Veja a minha resposta, você pode usar \ n como delimitador
Peter Smit
@wim Você precisaria implementar uma maneira de escapar do caractere delimitador, se ele puder ser um caractere legal. (E uma maneira de escapar qualquer personagem que você usa para escapar.)
jamesdlin
E se uma lista tiver um único elemento?
Sérgio Mafra
223

Também um pouco tarde, mas talvez seja útil para alguns. Estou usando uma combinação de ConfigParser e JSON:

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

basta ler com:

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

Você pode até quebrar linhas se sua lista for longa (obrigado @ peter-smit):

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

É claro que eu poderia usar o JSON, mas acho os arquivos de configuração muito mais legíveis e a seção [PADRÃO] muito útil.

quasimodo
fonte
1
É incrível porque ele "converte" automaticamente valores que podem ser úteis se você não conhecer os tipos antes.
LeGBT
Adoro essa idéia, mas só posso fazê-la funcionar com listas de números. As aspas não ajudam. Esquisito. Se movendo.
rsaw
5
Você precisará ter ["a", "b", "c"] para que as strings funcionem. Para mim, isso clica em números, mas como os arquivos cfg são principalmente editáveis ​​- adicionar "" toda vez que isso é uma dor. Prefiro usar vírgula e depois dividi-la.
Saurabh Hirani
Uma solução elegante usando apenas a biblioteca padrão. É bom poder usar comentários e json.
181
como isso funcionaria para strings não processadas, por exemplo key5 : [r"abc $x_i$", r"def $y_j$"]? Eles json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
geram
101

Chegando atrasado para esta festa, mas recentemente implementei isso com uma seção dedicada em um arquivo de configuração para uma lista:

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

e use config.items( "paths" )para obter uma lista iterável de itens de caminho, assim:

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

Espero que isso ajude outras pessoas pesquisando essa questão no Google;)

Henry Cooke
fonte
3
Eu gosto dessa solução, porque você pode ; commentretirar determinados itens da lista sem precisar reescrever a lista inteira.
wim
1
+1, mas se você fizer isso, basta ter cuidado com utilizando também key, como ConfigParser converte todas essas chaves para minúsculo
Alex Dean
4
@AlexDean Você pode configurar o ConfigParser para deixar o camelCase no lugar, definindo optionxform = str. Exemplo: config = ConfigParser.SafeConfigParser() config.optionxform = str Então o caso será deixado em paz
Cameron Goodale
@ Henry Cooke Você já testou isso quando uma chave é listada várias vezes?
DevPlayer 22/10/16
1
@DevPlayer Com o uso de várias teclas, você obtém apenas o último valor. (respondendo a 2 anos de idade comentário para benefício de outros leitores)
Marcin K
63

Uma coisa que muitas pessoas não sabem é que valores de configuração de várias linhas são permitidos. Por exemplo:

;test.ini
[hello]
barlist = 
    item1
    item2

O valor de config.get('hello','barlist')agora será:

"\nitem1\nitem2"

Que você pode facilmente dividir com o método splitlines (não se esqueça de filtrar itens vazios).

Se olharmos para um grande quadro como o Pyramid, eles estão usando 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

Fonte

Eu mesmo, talvez estenderia o ConfigParser se isso é algo comum para você:

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

Observe que há algumas coisas a serem observadas ao usar esta técnica

  1. Novas linhas que são itens devem começar com espaços em branco (por exemplo, um espaço ou uma guia)
  2. Todas as linhas a seguir que começam com espaço em branco são consideradas parte do item anterior. Além disso, se tiver um sinal = ou se começar com a; seguindo o espaço em branco.
Peter Smit
fonte
Por que você usa em .splitlines()vez de .split()? Usando o comportamento padrão de cada um, a divisão é claramente superior (filtra as linhas em branco). A menos que eu estou faltando alguma coisa ...
rsaw
7
.split () quebra em todos os espaços em branco (a menos que um caractere específico seja fornecido), .splitlines () quebra em todos os caracteres de nova linha.
Peter Smit
Ahhh bom ponto. Não pensei nisso, pois nenhum dos meus valores tinha espaços.
rsaw
38

Se você quiser passar literalmente uma lista, poderá usar:

ast.literal_eval()

Por exemplo configuração:

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

O código é:

import ConfigParser
import ast

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

resultado:

<type'list'>
["item1","item2","item3"]
PythonTester
fonte
Nesse caso, qual é a vantagem de usar ast.literal_eval()ao comparar com o (sem dúvida mais popular) json.loads()? Eu acho que o último fornece mais segurança, não?
precisa saber é
2
Eu adoraria ver e exemplo disso, sinta-se à vontade para adicionar uma resposta a este tópico, se você achar que isso ajudaria, embora seu comentário faça uma boa pergunta por si só. A resposta que eu dei simplifica o consumo de listas do ConfigParser, portanto é interno ao aplicativo, removendo a complicação de usar o regex. Eu não poderia comentar seu valor de "segurança" sem contexto.
PythonTester 27/01
Gostaria de ter cuidado usando literal_eval que esperar seqüência de python após = ou: portanto, você não pode usar mais eg path1 = / some / path / mas path1 = '/ some / path /'
vldbnc
21

Nenhuma menção ao converterskwargConfigParser() em qualquer uma dessas respostas foi bastante decepcionante.

De acordo com a documentação, você pode passar um dicionário para ConfigParserque adicione um getmétodo para os proxies do analisador e da seção. Então, para uma lista:

example.ini

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

Exemplo de analisador:

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']

Esse é o meu favorito, pois nenhuma subclasse é necessária e não preciso depender de um usuário final para escrever perfeitamente JSON ou uma lista que possa ser interpretada ast.literal_eval.

Grr
fonte
15

Eu cheguei aqui procurando consumir isso ...

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

A resposta é dividi-la na vírgula e retirar os espaços:

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

Para obter um resultado da lista:

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

Pode não responder exatamente à pergunta do OP, mas pode ser a resposta simples que algumas pessoas estão procurando.

John Mee
fonte
2
Eu pensei que Dick estivesse [email protected]! Não é de admirar que minhas correspondências continuem saltando! > _ <
Augusta
1
Lendo este comentário 4 anos depois e rindo do ovo da páscoa
um engenheiro curioso
11

Isto é o que eu uso para listas:

conteúdo do arquivo de configuração:

[sect]
alist = a
        b
        c

código:

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

funciona para cordas

em caso de números

conteúdo de configuração:

nlist = 1
        2
        3

código:

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

obrigado.

LittleEaster
fonte
Isso é o que eu estava realmente procurando graças @LittleEaster
ashley
5

Então, outra maneira, que eu prefiro, é apenas dividir os valores, por exemplo:

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

Pode ser carregado dessa maneira em uma lista de cadeias ou números inteiros, da seguinte maneira:

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(',')]

Esse método evita que você precise agrupar seus valores entre parênteses para carregar como JSON.

Mitch Gates
fonte
Oi Mitch, no último caso, não teria sido melhor usar get_int ('first_row'). Split (',') em vez de convertê-lo explicitamente em int durante o loop?
Guido
2

Apenas tipos primitivos são suportados para serialização pelo analisador de configuração. Eu usaria JSON ou YAML para esse tipo de requisito.

Utku ALTINKAYA
fonte
obrigado pelo esclarecimento, utku. o único problema é que não posso usar pacotes externos no momento. Eu acho que vou escrever uma classe simples para lidar com isso. eu vou compartilhar eventualmente.
Pistacchio 02/12/08
Qual versão do Python você está executando? O módulo JSON está incluído no 2.6.
Patrick Harrington
2

Eu enfrentei o mesmo problema no passado. Se você precisar de listas mais complexas, considere criar seu próprio analisador herdando do ConfigParser. Em seguida, você sobrescreveria o método get com isso:

    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

Com esta solução, você também poderá definir dicionários no seu arquivo de configuração.

Mas tenha cuidado! Isso não é tão seguro: significa que qualquer pessoa pode executar o código através do seu arquivo de configuração. Se a segurança não for um problema no seu projeto, eu consideraria o uso direto de classes python como arquivos de configuração. O seguinte é muito mais poderoso e descartável que um arquivo ConfigParser:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]
Mapad
fonte
No entanto, eu estava pensando em fazer isso: por que não configurar os valores de configuração como barList=item1,item2e depois chamar if value.find(',') > 0: return value.split(','), ou melhor ainda, fazer com que o aplicativo analise todas as opções de configuração como listas e .split(',')tudo cegamente?
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(',')

Então agora meu config.cfgarquivo, que poderia ser assim:

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

Pode ser analisado em objetos suficientemente refinados para o meu pequeno projeto.

>>> 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'

Isso é para uma análise muito rápida de configurações simples, você perde toda a capacidade de buscar entradas, bools e outros tipos de saída sem transformar o objeto retornado Parserou refazer o trabalho de análise realizado pela classe Parser em outro lugar.

Droogans
fonte
1

Concluí tarefa semelhante no meu projeto com seção com chaves sem 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)

Resultado:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
feeeper
fonte
0

json.loads& ast.literal_evalparece estar funcionando, mas uma lista simples dentro da configuração está tratando cada caractere como byte, retornando ainda um colchete ...

ou seja, se a configuração tiver fieldvalue = [1,2,3,4,5]

depois config.read(*.cfg) config['fieldValue'][0]retornando [no lugar de1

Abhishek Jain
fonte
0

Conforme mencionado por Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ), convém estender o ConfigParser; além disso, um interpolador pode ser usado para converter automaticamente para e da lista.

Para referência na parte inferior, você pode encontrar um código que converte automaticamente a configuração 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
  ]

Portanto, se você solicitar chaves, receberá:

<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 tenha em mente a importância da identificação. Conforme lido na sequência de documentos do ConfigParser:

Os valores podem abranger várias linhas, desde que sejam recuados mais profundamente que a primeira linha do valor. Dependendo do modo do analisador, as linhas em branco podem ser tratadas como partes de valores de múltiplas linhas ou ignoradas.

Dominik Maszczyk
fonte