Listes dans ConfigParser

182

Le fichier typique généré par ConfigParser ressemble à ceci:

[Section]
bar=foo
[Section 2]
bar2= baz

Maintenant, existe-t-il un moyen d'indexer des listes comme, par exemple:

[Section 3]
barList={
    item1,
    item2
}

Question connexe: Clés uniques ConfigParser de Python par section

pistache
la source

Réponses:

142

Rien ne vous empêche de regrouper la liste dans une chaîne délimitée, puis de la décompresser une fois que vous avez récupéré la chaîne de la configuration. Si vous le faisiez de cette façon, votre section de configuration ressemblerait à ceci:

[Section 3]
barList=item1,item2

Ce n'est pas joli mais c'est fonctionnel pour la plupart des listes simples.

David Locke
la source
2
Et si vous avez des listes complexes, vous pouvez vous référer à cette question: stackoverflow.com/questions/330900/… :-)
John Fouhy
belle solution, mais comment le faire s'il n'y a pas de délimiteur possible que vous ne pouvez garantir n'apparaîtra pas dans un élément de liste ???
wim
@wim Voir ma réponse, vous pouvez utiliser \ n comme délimiteur
Peter Smit
@wim Vous devez implémenter un moyen d'échapper au caractère délimiteur s'il peut s'agir d'un caractère légal. (Et un moyen d'échapper au personnage que vous utilisez pour vous échapper.)
jamesdlin
Et si une liste a un seul élément?
Sérgio Mafra
223

Également un peu tard, mais peut-être utile pour certains. J'utilise une combinaison de ConfigParser et JSON:

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

il suffit de le lire avec:

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

Vous pouvez même casser des lignes si votre liste est longue (merci @ peter-smit):

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

Bien sûr, je pourrais simplement utiliser JSON, mais je trouve les fichiers de configuration beaucoup plus lisibles et la section [DEFAULT] très pratique.

quasimodo
la source
1
C'est génial car il fait automatiquement "cast" des valeurs qui peuvent être utiles si vous ne connaissez pas les types à l'avance.
LeGBT
J'adore cette idée, mais je ne peux la faire fonctionner qu'avec des listes de nombres. Les guillemets n'aident pas. Bizarre. Passer à autre chose.
rsaw
5
Vous devrez avoir ["a", "b", "c"] pour que les chaînes fonctionnent. Pour moi, cela clique pour les nombres mais comme les fichiers cfg sont pour la plupart modifiables - ajouter "" à chaque fois est pénible. Je préfère utiliser une virgule et ensuite la diviser.
Saurabh Hirani
Une solution élégante utilisant uniquement la bibliothèque standard. C'est bien de pouvoir utiliser les commentaires et json.
wi1
comment cela fonctionnerait-il pour les chaînes brutes, par exemple key5 : [r"abc $x_i$", r"def $y_j$"]? Ils soulèvent l'erreurjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu
101

Arriver en retard à cette fête, mais j'ai récemment mis en œuvre cela avec une section dédiée dans un fichier de configuration pour une liste:

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

et en utilisant config.items( "paths" )pour obtenir une liste itérative d'éléments de chemin, comme ceci:

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

J'espère que cela aidera d'autres personnes à googler cette question;)

Henry Cooke
la source
3
J'aime cette solution, car vous pouvez ; commentsortir certains éléments de la liste sans avoir à réécrire toute la liste.
wim
1
+1, mais si vous faites cela, faites juste attention à utiliser également key, car ConfigParser convertit toutes ces clés en minuscules
Alex Dean
4
@AlexDean Vous pouvez configurer ConfigParser pour laisser le camelCase en place en définissant optionxform = str. Exemple: config = ConfigParser.SafeConfigParser() config.optionxform = str Ensuite, l'affaire sera laissée seule
Cameron Goodale
@Henry Cooke Avez-vous testé cela lorsqu'une clé est répertoriée plusieurs fois?
DevPlayer
1
@DevPlayer Avec l'utilisation de plusieurs clés, vous n'obtenez que la dernière valeur. (répondant à un commentaire de 2 ans au profit des autres lecteurs)
Marcin K
63

Une chose que beaucoup de gens ne savent pas, c'est que les valeurs de configuration multilignes sont autorisées. Par exemple:

;test.ini
[hello]
barlist = 
    item1
    item2

La valeur de config.get('hello','barlist')sera désormais:

"\nitem1\nitem2"

Que vous pouvez facilement diviser avec la méthode des lignes fractionnées (n'oubliez pas de filtrer les éléments vides).

Si nous regardons un grand framework comme Pyramid, ils utilisent cette technique:

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

La source

Moi-même, j'étendrais peut-être le ConfigParser si c'est une chose courante pour vous:

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

Notez qu'il y a quelques points à surveiller lors de l'utilisation de cette technique

  1. Les nouvelles lignes qui sont des éléments doivent commencer par des espaces (par exemple un espace ou une tabulation)
  2. Toutes les lignes suivantes commençant par un espace sont considérées comme faisant partie de l'élément précédent. Aussi s'il a un signe = ou s'il commence par un; suivant l'espace blanc.
Peter Smit
la source
Pourquoi utilisez-vous à la .splitlines()place de .split()? En utilisant le comportement par défaut de chacun, la division est clairement supérieure (filtre les lignes vides). Sauf si je manque quelque chose ...
rsaw
7
.split () interrompt tous les espaces (sauf si un caractère spécifique est donné), .splitlines () interrompt tous les caractères de nouvelle ligne.
Peter Smit
Ahhh bon point. Je n'y ai pas pensé car aucune de mes valeurs n'avait d'espace.
rsaw le
38

Si vous voulez passer littéralement dans une liste, vous pouvez utiliser:

ast.literal_eval()

Par exemple de configuration:

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

Le code est:

import ConfigParser
import ast

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

production:

<type'list'>
["item1","item2","item3"]
PythonTester
la source
Dans ce cas, quel est l'avantage d'utiliser ast.literal_eval()lors de la comparaison pour utiliser le (sans doute plus populaire) json.loads()? Je pense que ce dernier offre plus de sécurité, non?
RayLuo
2
J'adorerais voir un exemple de cela, n'hésitez pas à ajouter une réponse à ce fil si vous pensez que cela aiderait, bien que votre commentaire ferait une bonne question en soi. La réponse que j'ai donnée simplifie la consommation de listes de ConfigParser, elle est donc interne à l'application en supprimant la comlication de l'utilisation de regex. Je ne pourrais pas commenter sa valeur de «sécurité» sans contexte.
PythonTester
Je ferais attention en utilisant literal_eval qui attend une chaîne python après = ou: par conséquent, vous ne pouvez plus utiliser, par exemple, path1 = / some / path / but path1 = '/ some / path /'
vldbnc
21

Aucune mention du converterskwarg pourConfigParser() dans aucune de ces réponses n'était plutôt décevante.

Selon la documentation, vous pouvez passer un dictionnaire ConfigParserqui ajoutera unget méthode pour l'analyseur et les proxys de section. Donc pour une liste:

example.ini

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

Exemple d'analyseur:

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

C'est mon préféré car aucun sous-classement n'est nécessaire et je n'ai pas besoin de compter sur un utilisateur final pour écrire parfaitement JSON ou une liste qui peut être interprétée par ast.literal_eval.

Grr
la source
15

J'ai atterri ici en cherchant à consommer ça ...

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

La réponse est de le diviser par la virgule et de supprimer les espaces:

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

Pour obtenir un résultat de liste:

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

Cela peut ne pas répondre exactement à la question du PO, mais pourrait être la réponse simple que certaines personnes recherchent.

John Mee
la source
2
Je pensais que Dick était à [email protected]! Pas étonnant que mon courrier ne cesse de rebondir! > _ <
Augusta
1
Lire ce commentaire 4 ans plus tard et rire à l'œuf de Pâques
un ingénieur curieux le
11

Voici ce que j'utilise pour les listes:

contenu du fichier de configuration:

[sect]
alist = a
        b
        c

code:

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

ça marche pour les cordes

en cas de nombres

contenu de la configuration:

nlist = 1
        2
        3

code:

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

Merci.

LittleEaster
la source
C'est celui que je cherchais réellement merci @LittleEaster
ashley
5

Donc, une autre façon, que je préfère, est de simplement diviser les valeurs, par exemple:

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

Pourrait être chargé comme ceci dans une liste de chaînes ou d'entiers, comme suit:

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

Cette méthode vous évite d'avoir à placer vos valeurs entre crochets pour les charger en JSON.

Mitch Gates
la source
Salut Mitch, dans ce dernier cas, il n'aurait pas été plus agréable d'utiliser get_int ('first_row'). Split (',') au lieu de le convertir explicitement en int en boucle?
Guido du
2

Seuls les types primitifs sont pris en charge pour la sérialisation par l'analyseur de configuration. J'utiliserais JSON ou YAML pour ce type d'exigence.

M. Utku ALTINKAYA
la source
merci pour la clarification, utku. le seul problème est que je ne peux pas utiliser de packages externes pour le moment. Je pense que je vais écrire une classe simple pour gérer ça. je le partagerai éventuellement.
pistacchio
Quelle version de Python utilisez-vous? Le module JSON est inclus avec 2.6.
Patrick Harrington
2

J'ai été confronté au même problème dans le passé. Si vous avez besoin de listes plus complexes, pensez à créer votre propre analyseur en héritant de ConfigParser. Ensuite, vous écraseriez la méthode get avec cela:

    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

Avec cette solution, vous pourrez également définir des dictionnaires dans votre fichier de configuration.

Mais fais attention! Ce n'est pas aussi sûr: cela signifie que n'importe qui peut exécuter du code via votre fichier de configuration. Si la sécurité n'est pas un problème dans votre projet, j'envisagerais d'utiliser directement des classes python comme fichiers de configuration. Ce qui suit est beaucoup plus puissant et consommable qu'un fichier ConfigParser:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]
Mapad
la source
Je pensais faire ceci, cependant: pourquoi ne pas avoir les valeurs de configuration configurées comme barList=item1,item2puis appeler if value.find(',') > 0: return value.split(','), ou mieux encore, faire analyser toutes les options de configuration sous forme de listes, et .split(',')tout simplement à l' aveugle?
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(',')

Alors maintenant mon config.cfgfichier, qui pourrait ressembler à ceci:

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

Peut être analysé en objets suffisamment fins pour mon petit projet.

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

C'est pour une analyse très rapide de configurations simples, vous perdez toute capacité à récupérer des entiers, des booléens et d'autres types de sortie sans transformer l'objet renvoyé par Parser, ni refaire le travail d'analyse effectué par la classe Parser ailleurs.

Droogans
la source
1

J'ai terminé une tâche similaire dans mon projet avec une section avec des clés sans valeurs:

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)

Production:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
faible
la source
0

json.loads & ast.literal_eval semble fonctionner mais une simple liste dans la configuration traite chaque caractère comme un octet, ce qui renvoie même un crochet ...

signifiant si la configuration a fieldvalue = [1,2,3,4,5]

puis config.read(*.cfg) config['fieldValue'][0]revenant [à la place de1

Abhishek Jain
la source
0

Comme mentionné par Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ) Vous voudrez peut-être étendre ConfigParser, en outre, un Interpolator peut être utilisé pour convertir automatiquement dans et à partir de la liste.

Pour référence en bas, vous pouvez trouver du code qui convertit automatiquement la configuration comme:

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

Donc, si vous demandez des clés, vous obtiendrez:

<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 garde à l'esprit l'importance de l'indentation. Comme indiqué dans la chaîne de documentation ConfigParser:

Les valeurs peuvent s'étendre sur plusieurs lignes, à condition qu'elles soient plus profondes que la première ligne de la valeur. Selon le mode de l'analyseur, les lignes vides peuvent être traitées comme des parties de valeurs multilignes ou ignorées.

Dominik Maszczyk
la source