Listen in ConfigParser

Antworten:

141

Nichts hindert Sie daran, die Liste in eine begrenzte Zeichenfolge zu packen und sie dann zu entpacken, sobald Sie die Zeichenfolge aus der Konfiguration erhalten haben. Wenn Sie es so machen würden, würde Ihr Konfigurationsabschnitt folgendermaßen aussehen:

[Section 3]
barList=item1,item2

Es ist nicht schön, aber es funktioniert für die meisten einfachen Listen.

David Locke
quelle
2
Und wenn Sie komplexe Listen haben, können Sie sich auf diese Frage beziehen: stackoverflow.com/questions/330900/… :-)
John Fouhy
nette Lösung, aber wie geht das, wenn es kein mögliches Trennzeichen gibt, von dem Sie garantieren können, dass es nicht in einem Listenelement erscheint?
wim
@wim Siehe meine Antwort, Sie können \ n als Trennzeichen verwenden
Peter Smit
@wim Sie müssten eine Möglichkeit implementieren, um das Trennzeichen zu umgehen, wenn es sich um ein legales Zeichen handeln kann. (Und ein Weg, um jedem Charakter zu entkommen, den Sie für die Flucht verwenden.)
Jamesdlin
Was ist, wenn eine Liste ein einzelnes Element enthält?
Sérgio Mafra
222

Auch etwas spät, aber vielleicht hilfreich für einige. Ich verwende eine Kombination aus ConfigParser und JSON:

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

Lies es einfach mit:

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

Sie können sogar Zeilen brechen, wenn Ihre Liste lang ist (danke @ peter-smit):

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

Natürlich könnte ich nur JSON verwenden, aber ich finde Konfigurationsdateien viel besser lesbar und den Abschnitt [DEFAULT] sehr praktisch.

Quasimodo
quelle
1
Es ist fantastisch, weil es automatisch Werte "umwandelt", was nützlich sein kann, wenn Sie die Typen vorher nicht kennen.
LeGBT
Ich liebe diese Idee, aber ich kann sie nur mit Zahlenlisten zum Laufen bringen. Anführungszeichen helfen nicht. Seltsam. Weitermachen.
rsaw
5
Sie müssen ["a", "b", "c"] haben, damit die Zeichenfolgen funktionieren. Für mich klickt dies auf Zahlen, aber da CFG-Dateien meistens bearbeitet werden können, ist das Hinzufügen von "" jedes Mal ein Schmerz. Ich würde lieber Komma verwenden und es dann teilen.
Saurabh Hirani
Eine elegante Lösung, die nur die Standardbibliothek verwendet. Schön, Kommentare und JSON verwenden zu können.
wi1
Wie würde dies für rohe Saiten funktionieren, z key5 : [r"abc $x_i$", r"def $y_j$"]. Sie erheben den Fehlerjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu
100

Ich komme zu spät zu dieser Party, aber ich habe dies kürzlich mit einem speziellen Abschnitt in einer Konfigurationsdatei für eine Liste implementiert:

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

und Verwenden config.items( "paths" ), um eine iterierbare Liste von Pfadelementen zu erhalten, wie folgt:

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

Hoffe das hilft anderen Leuten diese Frage zu googeln;)

Henry Cooke
quelle
3
Ich mag diese Lösung, weil Sie ; commentbestimmte Elemente aus der Liste entfernen können , ohne die gesamte Liste neu schreiben zu müssen.
wim
1
+1, aber wenn Sie dies tun, seien Sie vorsichtig, wenn Sie auch verwenden key, da ConfigParser alle diese Schlüssel in Kleinbuchstaben konvertiert
Alex Dean
4
@AlexDean Sie können den ConfigParser so einrichten, dass der camelCase an Ort und Stelle bleibt, indem Sie optionxform = str festlegen. Beispiel: config = ConfigParser.SafeConfigParser() config.optionxform = str Dann wird der Fall in Ruhe gelassen
Cameron Goodale
@ Henry Cooke Haben Sie das getestet, wenn ein Schlüssel mehrmals aufgeführt ist?
DevPlayer
1
@ DevPlayer Bei Verwendung mehrerer Schlüssel erhalten Sie nur den letzten Wert. (Antwort auf 2 Jahre alten Kommentar zum Nutzen anderer Leser)
Marcin K
61

Eine Sache, die viele Leute nicht wissen, ist, dass mehrzeilige Konfigurationswerte erlaubt sind. Beispielsweise:

;test.ini
[hello]
barlist = 
    item1
    item2

Der Wert von config.get('hello','barlist')wird jetzt sein:

"\nitem1\nitem2"

Was Sie leicht mit der Splitlines-Methode teilen können (vergessen Sie nicht, leere Elemente zu filtern).

Wenn wir uns ein großes Framework wie Pyramid ansehen, verwenden sie diese Technik:

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

Quelle

Ich selbst würde den ConfigParser vielleicht erweitern, wenn dies für Sie üblich ist:

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

Beachten Sie, dass bei dieser Technik einige Dinge zu beachten sind

  1. Neue Zeilen, die Elemente sind, sollten mit Leerzeichen beginnen (z. B. ein Leerzeichen oder eine Registerkarte).
  2. Alle folgenden Zeilen, die mit Leerzeichen beginnen, werden als Teil des vorherigen Elements betrachtet. Auch wenn es ein = Zeichen hat oder wenn es mit einem beginnt; Folgen Sie dem Leerzeichen.
Peter Smit
quelle
Warum benutzt du .splitlines()statt .split()? Bei Verwendung des Standardverhaltens ist die Aufteilung eindeutig überlegen (filtert Leerzeilen heraus). Es sei denn, ich vermisse etwas ...
rsaw
7
.split () wird in allen Leerzeichen unterbrochen (sofern kein bestimmtes Zeichen angegeben ist) .splitlines () wird in allen Zeilenumbrüchen unterbrochen.
Peter Smit
Ahhh guter Punkt. Ich habe nicht darüber nachgedacht, da keiner meiner Werte Leerzeichen hatte.
rsaw
38

Wenn Sie eine Liste buchstäblich übergeben möchten, können Sie Folgendes verwenden:

ast.literal_eval()

Zum Beispiel Konfiguration:

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

Der Code lautet:

import ConfigParser
import ast

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

Ausgabe:

<type'list'>
["item1","item2","item3"]
PythonTester
quelle
Was ist in diesem Fall der Vorteil der Verwendung ast.literal_eval()im Vergleich zur Verwendung der (wohl populäreren) json.loads()? Ich denke, letzteres bietet mehr Sicherheit, nicht wahr?
RayLuo
2
Ich würde gerne ein Beispiel dafür sehen und gerne eine Antwort auf diesen Thread hinzufügen, wenn Sie der Meinung sind, dass dies hilfreich wäre, obwohl Ihr Kommentar an sich schon eine gute Frage wäre. Die Antwort, die ich gegeben habe, vereinfacht den Verbrauch von Listen aus ConfigParser und ist daher für die App intern, sodass die Verwendung von Regex nicht mehr erforderlich ist. Ich konnte seinen "Sicherheitswert" nicht ohne Kontext kommentieren.
PythonTester
Ich würde vorsichtig sein, wenn ich literal_eval verwende, das eine Python-Zeichenfolge nach = oder: erwartet. Daher können Sie zB path1 = / some / path / nicht mehr verwenden, aber path1 = '/ some / path /'
vldbnc
20

Keine Erwähnung des convertersKwargConfigParser() in einer dieser Antworten war eher enttäuschend.

Gemäß der Dokumentation können Sie ein Wörterbuch übergeben, ConfigParserdas eine getMethode sowohl für den Parser- als auch für den Abschnitts-Proxy hinzufügt . Also für eine Liste:

example.ini

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

Parser-Beispiel:

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

Dies ist mein persönlicher Favorit, da keine Unterklassen erforderlich sind und ich mich nicht auf einen Endbenutzer verlassen muss, um JSON oder eine Liste, die von interpretiert werden kann, perfekt zu schreiben ast.literal_eval.

Grr
quelle
15

Ich bin hier gelandet, um das zu konsumieren ...

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

Die Antwort ist, es auf dem Komma zu teilen und die Leerzeichen zu entfernen:

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

So erhalten Sie ein Listenergebnis:

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

Es beantwortet möglicherweise nicht genau die Frage des OP, ist jedoch möglicherweise die einfache Antwort, nach der einige Leute suchen.

John Mee
quelle
2
Ich dachte Dick wäre dabei [email protected]! Kein Wunder, dass meine Post immer wieder abprallte! > _ <
Augusta
1
Lesen Sie diesen Kommentar 4 Jahre später und kichern Sie über das Osterei
ein neugieriger Ingenieur
11

Folgendes verwende ich für Listen:

Inhalt der Konfigurationsdatei:

[sect]
alist = a
        b
        c

Code:

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

es funktioniert für Strings

im Falle von Zahlen

Konfigurationsinhalt:

nlist = 1
        2
        3

Code:

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

Vielen Dank.

LittleEaster
quelle
Dies ist die, nach der ich eigentlich gesucht habe, danke @LittleEaster
ashley
5

Eine andere Möglichkeit, die ich bevorzuge, besteht darin, die Werte einfach aufzuteilen, zum Beispiel:

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

Könnte wie folgt in eine Liste von Zeichenfolgen oder Ganzzahlen geladen werden:

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

Diese Methode verhindert, dass Sie Ihre Werte in Klammern setzen müssen, um sie als JSON zu laden.

Mitch Gates
quelle
Hallo Mitch, im letzteren Fall wäre es nicht besser gewesen, get_int ('first_row'). Split (',') zu verwenden, als es während des Loopings explizit in int zu konvertieren?
Guido
2

Für die Serialisierung durch den Konfigurationsparser werden nur primitive Typen unterstützt. Ich würde JSON oder YAML für diese Art von Anforderung verwenden.

M. Utku ALTINKAYA
quelle
danke für die klärung, utku. Das einzige Problem ist, dass ich im Moment keine externen Pakete verwenden kann. Ich denke, ich werde eine einfache Klasse schreiben, um damit umzugehen. Ich werde es irgendwann teilen.
Pistacchio
Welche Version von Python verwenden Sie? Das JSON-Modul ist in 2.6 enthalten.
Patrick Harrington
2

Ich hatte in der Vergangenheit das gleiche Problem. Wenn Sie komplexere Listen benötigen, sollten Sie einen eigenen Parser erstellen, indem Sie von ConfigParser erben. Dann würden Sie die get-Methode damit überschreiben:

    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

Mit dieser Lösung können Sie auch Wörterbücher in Ihrer Konfigurationsdatei definieren.

Aber sei vorsichtig! Dies ist nicht so sicher: Dies bedeutet, dass jeder Code über Ihre Konfigurationsdatei ausführen kann. Wenn Sicherheit in Ihrem Projekt kein Problem darstellt, würde ich in Betracht ziehen, Python-Klassen direkt als Konfigurationsdateien zu verwenden. Folgendes ist viel leistungsfähiger und entbehrlicher als eine ConfigParser-Datei:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]
Mapad
quelle
Ich dachte jedoch darüber nach: Warum sollten die Konfigurationswerte nicht wie folgt eingerichtet barList=item1,item2und dann aufgerufen werden if value.find(',') > 0: return value.split(','), oder noch besser, die Anwendung muss alle Konfigurationsoptionen als Listen analysieren und einfach .split(',')alles blind?
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(',')

Also jetzt meine config.cfgDatei, die so aussehen könnte:

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

Kann für mein kleines Projekt in feinkörnige Objekte analysiert werden.

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

Dies dient zum sehr schnellen Parsen einfacher Konfigurationen. Sie verlieren alle Möglichkeiten, Ints, Bools und andere Ausgabetypen abzurufen, ohne das von der zurückgegebenen Objekt zurückgegebene Objekt zu transformieren Parseroder den von der Parser-Klasse an anderer Stelle ausgeführten Parsing-Job erneut auszuführen.

Droogans
quelle
1

Ich habe eine ähnliche Aufgabe in meinem Projekt mit einem Abschnitt mit Schlüsseln ohne Werte abgeschlossen:

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)

Ausgabe:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
Feeeper
quelle
0

json.loads& ast.literal_evalscheint zu funktionieren, aber eine einfache Liste in der Konfiguration behandelt jedes Zeichen als Byte und gibt sogar eine eckige Klammer zurück.

was bedeutet, wenn config hat fieldvalue = [1,2,3,4,5]

dann config.read(*.cfg) config['fieldValue'][0]Rückkehr [anstelle von1

Abhishek Jain
quelle
0

Wie von Peter Smit erwähnt ( https://stackoverflow.com/a/11866695/7424596 ) Möglicherweise möchten Sie ConfigParser erweitern. Außerdem kann ein Interpolator zum automatischen Konvertieren in und aus der Liste verwendet werden.

Als Referenz finden Sie unten Code, der die Konfiguration automatisch wie folgt konvertiert:

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

Wenn Sie also Schlüssel anfordern, erhalten Sie:

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

Code:

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 denken an die Wichtigkeit der Einrückung. Wie in ConfigParser doc string gelesen:

Werte können mehrere Zeilen umfassen, sofern sie tiefer als die erste Zeile des Werts eingerückt sind. Abhängig vom Parser-Modus können Leerzeilen als Teile mehrzeiliger Werte behandelt oder ignoriert werden.

Dominik Maszczyk
quelle