Elenchi in ConfigParser


Risposte:


142

Non c'è nulla che ti impedisca di comprimere l'elenco in una stringa delimitata e quindi scompattarlo una volta ottenuta la stringa dalla configurazione. Se lo facessi in questo modo la tua sezione di configurazione sarebbe simile a:

[Section 3]
barList=item1,item2

Non è carino ma è funzionale per la maggior parte degli elenchi semplici.


2
E se hai elenchi complessi, puoi fare riferimento a questa domanda: stackoverflow.com/questions/330900/… :-)
John Fouhy,

bella soluzione, ma come farlo se non esiste un delimitatore possibile di quanto si possa garantire non comparirà all'interno di un elemento dell'elenco ???
mercoledì

@wim Vedi la mia risposta, puoi usare \ n come delimitatore
Peter Smit

@wim Dovresti implementare un modo per sfuggire al carattere delimitatore se può essere un personaggio legale. (E un modo per sfuggire a qualsiasi personaggio che usi per scappare.)
jamesdlin

Cosa succede se un elenco ha un singolo elemento?
Sérgio Mafra il

223

Anche un po 'in ritardo, ma forse utile per alcuni. Sto usando una combinazione di ConfigParser e JSON:

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

basta leggerlo con:

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

Puoi anche interrompere le linee se la tua lista è lunga (grazie a @ peter-smit):

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

Ovviamente potrei semplicemente usare JSON, ma trovo i file di configurazione molto più leggibili e la sezione [DEFAULT] molto utile.


1
È fantastico perché "cast" automaticamente valori che possono essere utili se non si conoscono in anticipo i tipi.
LeGBT,

Adoro questa idea, ma posso farla funzionare solo con elenchi di numeri. Le virgolette non aiutano. Strano. Andare avanti.
visto il

5
Dovrai avere ["a", "b", "c"] affinché le stringhe funzionino. Per me, questo fa clic per i numeri, ma dato che i file cfg sono per lo più modificabili - l'aggiunta di "" ogni volta è un dolore. Preferirei usare la virgola e poi dividerla.
Saurabh Hirani,

Una soluzione elegante che utilizza solo la libreria standard. Bello poter usare commenti e json.
°

come funzionerebbe per le corde grezze, ad es. key5 : [r"abc $x_i$", r"def $y_j$"]? json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
Sollevano

101

Venendo in ritardo a questa festa, ma di recente ho implementato questo con una sezione dedicata in un file di configurazione per un elenco:

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

e usando config.items( "paths" )per ottenere un elenco iterabile di elementi del percorso, in questo modo:

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

Spero che questo aiuti altre persone a cercare su Google questa domanda;)


3
Mi piace questa soluzione, perché è possibile ; commentestrarre determinati elementi dall'elenco senza dover riscrivere l'intero elenco.
mercoledì

1
+1, ma se lo fai, fai attenzione anche all'uso key, poiché ConfigParser converte tutti questi tasti in minuscolo
Alex Dean,

4
@AlexDean Puoi configurare ConfigParser per lasciare il camelCase in posizione impostando optionxform = str. Esempio: config = ConfigParser.SafeConfigParser() config.optionxform = str Quindi il caso sarà lasciato solo
Cameron Goodale,

@Henry Cooke L'hai provato quando una chiave viene elencata più volte?
DevPlayer il

1
@DevPlayer Con l'utilizzo di più chiavi si ottiene solo l'ultimo valore. (rispondendo al vecchio commento di 2 anni a beneficio di altri lettori)
Marcin K,

63

Una cosa che molte persone non sanno è che sono ammessi valori di configurazione multilinea. Per esempio:

;test.ini
[hello]
barlist = 
    item1
    item2

Il valore di config.get('hello','barlist')sarà ora:

"\nitem1\nitem2"

Che puoi facilmente dividere con il metodo splitlines (non dimenticare di filtrare gli oggetti vuoti).

Se guardiamo a un grande framework come Pyramid stanno usando questa tecnica:

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

Io stesso, estenderei forse ConfigParser se questa è una cosa comune per te:

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

Nota che ci sono alcune cose a cui prestare attenzione quando usi questa tecnica

  1. Le nuove linee che sono elementi dovrebbero iniziare con spazi bianchi (ad esempio uno spazio o una scheda)
  2. Tutte le righe seguenti che iniziano con uno spazio bianco sono considerate parte dell'elemento precedente. Anche se ha un segno = o se inizia con un; seguendo lo spazio bianco.

Perché usi .splitlines()invece di .split()? Utilizzando il comportamento predefinito di ciascuno, la divisione è chiaramente superiore (filtra le righe vuote). A meno che non mi manchi qualcosa ...
visto il

7
.split () si interrompe su tutti gli spazi bianchi (a meno che non venga specificato un carattere specifico), .splitlines () si interrompe su tutti i caratteri di nuova riga.
Peter Smit,

Ahhh buon punto. Non ci ho pensato perché nessuno dei miei valori aveva spazi.
visto il

38

Se vuoi letteralmente passare un elenco, puoi usare:

ast.literal_eval()

Ad esempio configurazione:

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

Il codice è:

import ConfigParser
import ast

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

produzione:

<type'list'>
["item1","item2","item3"]

In questo caso, qual è il vantaggio dell'utilizzo ast.literal_eval()rispetto al (probabilmente più popolare) json.loads()? Penso che quest'ultimo fornisca maggiore sicurezza, no?
RayLuo,

2
Mi piacerebbe vedere ed esempio di questo, sentiti libero di aggiungere una risposta a questa discussione se ritieni che possa aiutare, anche se il tuo commento sarebbe di per sé una buona domanda. La risposta che ho dato semplifica il consumo di elenchi da ConfigParser, quindi è interna all'app che rimuove la complicanza dell'utilizzo di regex. Non ho potuto commentare il suo valore di "sicurezza" senza contesto.
PythonTester,

Starei attento a usare literal_eval che prevede la stringa di pitone dopo = o: quindi non puoi più usare ad esempio path1 = / some / path / but path1 = '/ some / path /'
vldbnc

21

Nessuna menzione del converterskwarg perConfigParser() in nessuna di queste risposte è stata piuttosto deludente.

Secondo la documentazione è possibile passare un dizionario a ConfigParserquello che aggiungerà un getmetodo sia per il parser che per i proxy di sezione. Quindi per un elenco:

example.ini

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

Esempio di parser:

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

Questo è il mio preferito in quanto non è necessaria alcuna sottoclasse e non devo fare affidamento su un utente finale per scrivere perfettamente JSON o un elenco che può essere interpretato da ast.literal_eval.


15

Sono atterrato qui cercando di consumare questo ...

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

La risposta è dividerlo sulla virgola e rimuovere gli spazi:

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

Per ottenere un risultato dell'elenco:

['richard.sorge@cccp.gov', 'mata.hari@deutschland.gov']

Potrebbe non rispondere esattamente alla domanda del PO, ma potrebbe essere la risposta semplice che alcune persone stanno cercando.


2
Pensavo che fosse Dick sorger@espionage.su! Nessuna meraviglia che la mia posta continuasse a rimbalzare! > _ <
Augusta

1
Leggendo questo commento 4 anni dopo e ridacchiando all'uovo di Pasqua
un curioso ingegnere il

11

Questo è quello che uso per le liste:

contenuto del file di configurazione:

[sect]
alist = a
        b
        c

codice :

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

funziona per le stringhe

in caso di numeri

contenuto della configurazione:

nlist = 1
        2
        3

codice:

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

Grazie.


Questo è quello che stavo cercando grazie @LittleEaster
ashley,

5

Quindi un altro modo, che preferisco, è quello di dividere i valori, ad esempio:

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

Potrebbe essere caricato in questo modo in un elenco di stringhe o numeri interi, come segue:

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

Questo metodo impedisce di dover racchiudere i valori tra parentesi per caricarli come JSON.


Ciao Mitch, in quest'ultimo caso non sarebbe stato meglio usare get_int ('first_row'). Split (',') invece di convertirlo esplicitamente in int durante il loop?
Guido,

2

Solo i tipi primitivi sono supportati per la serializzazione dal parser di configurazione. Vorrei usare JSON o YAML per quel tipo di requisito.


grazie per il chiarimento, utku. l'unico problema è che al momento non posso usare pacchetti esterni. penso che scriverò una semplice lezione per gestirlo. lo condividerò alla fine.
pistacchio,

Quale versione di Python stai eseguendo? Il modulo JSON è incluso in 2.6.
Patrick Harrington,

2

Ho affrontato lo stesso problema in passato. Se hai bisogno di elenchi più complessi, considera la possibilità di creare il tuo parser ereditando da ConfigParser. Quindi sovrascriveresti il ​​metodo get con quello:

    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 questa soluzione sarai anche in grado di definire dizionari nel tuo file di configurazione.

Ma fa attenzione! Questo non è così sicuro: questo significa che chiunque potrebbe eseguire il codice attraverso il tuo file di configurazione. Se la sicurezza non è un problema nel tuo progetto, prenderei in considerazione l'utilizzo diretto delle classi Python come file di configurazione. Quanto segue è molto più potente e spendibile di un file ConfigParser:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]

Stavo pensando di farlo, tuttavia: perché non impostare i valori di configurazione come barList=item1,item2e quindi chiamare if value.find(',') > 0: return value.split(','), o meglio ancora, fare in modo che l'applicazione analizzi tutte le opzioni di configurazione come elenchi e .split(',')tutto ciecamente?
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(',')

Quindi ora il mio config.cfgfile, che potrebbe assomigliare a questo:

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

Può essere analizzato in oggetti a grana fine per il mio piccolo progetto.

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

Questo per l'analisi molto rapida di configurazioni semplici, perdi tutta la capacità di recuperare ints, bool e altri tipi di output senza trasformare l'oggetto da cui viene restituito Parsero ripetere il lavoro di analisi realizzato dalla classe Parser altrove.


1

Ho completato un'attività simile nel mio progetto con una sezione con chiavi senza valori:

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)

Produzione:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn

0

json.loads & ast.literal_eval sembra funzionare, ma un semplice elenco all'interno di config tratta ogni carattere come byte in modo da restituire anche la parentesi quadra ....

significato se config ha fieldvalue = [1,2,3,4,5]

poi config.read(*.cfg) config['fieldValue'][0]ritornando [al posto di1


0

Come menzionato da Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ) Potresti voler estendere ConfigParser, inoltre, un Interpolator può essere usato per convertire automaticamente in e dall'elenco.

Per riferimento in basso puoi trovare il codice che converte automaticamente la configurazione come:

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

Quindi se richiedi le chiavi otterrai:

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

Codice:

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 tieni a mente l'importanza dell'indentazione. Come si legge nella stringa di documenti ConfigParser:

I valori possono estendersi su più righe, purché siano rientrate più in profondità rispetto alla prima riga del valore. A seconda della modalità del parser, le righe vuote possono essere trattate come parti di valori multilinea o ignorate.

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.