Списки в ConfigParser


183

Типичный сгенерированный файл ConfigParser выглядит так:

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

Теперь, есть ли способ индексировать списки, такие как, например:

[Section 3]
barList={
    item1,
    item2
}

Смежный вопрос: уникальные ключи Python ConfigParser для каждого раздела

Ответы:


143

Ничто не мешает вам упаковать список в строку с разделителями, а затем распаковать его, как только вы получите строку из конфигурации. Если бы вы сделали это таким образом, ваш раздел конфигурации будет выглядеть так:

[Section 3]
barList=item1,item2

Это не красиво, но это функционально для большинства простых списков.


2
И если у вас есть сложные списки, вы можете обратиться к этому вопросу: stackoverflow.com/questions/330900/… :-)
Джон Фухи

Хорошее решение, но как это сделать, если нет возможного разделителя, который, как вы можете гарантировать, не появится внутри элемента списка ???
Вим

@wim Посмотрите мой ответ, вы можете использовать \ n в качестве разделителя
Питер Смит

@wim Вам необходимо реализовать способ экранирования символа-разделителя, если он может быть допустимым. (И способ убежать от любого персонажа, которого вы используете для побега.)
jamesdlin

Что если в списке есть один элемент?
Сержио Мафра

224

Также немного поздно, но может быть полезно для некоторых. Я использую комбинацию ConfigParser и JSON:

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

просто прочитайте это с:

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

Вы можете даже разбить строки, если ваш список длинный (спасибо @ peter-smit):

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

Конечно, я мог бы просто использовать JSON, но я нахожу файлы конфигурации гораздо более удобочитаемыми, а раздел [DEFAULT] очень удобным.


1
Это здорово, потому что он автоматически «приводит» значения, которые могут быть полезны, если вы не знаете типы заранее.
LeGBT

Мне нравится эта идея, но я могу заставить ее работать только со списками чисел. Кавычки не помогают. Weird. Двигаемся дальше.
rsaw

5
Вы должны будете иметь ["a", "b", "c"] для строк, чтобы они работали. Для меня это клики для цифр, но поскольку cfg файлы в основном редактируемы - добавление "" каждый раз является болью. Я бы предпочел использовать запятую, а затем разделить ее.
Саураб Хирани

Элегантное решение с использованием только стандартной библиотеки. Приятно иметь возможность использовать комментарии и JSON.
августа,

как это работает для необработанных строк, например key5 : [r"abc $x_i$", r"def $y_j$"]? Они поднимают ошибкуjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu

101

Я опаздываю на эту вечеринку, но недавно я реализовал это с помощью специального раздела в файле конфигурации для списка:

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

и использовать config.items( "paths" )для получения итеративного списка элементов пути, например так:

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

Надеюсь, это поможет другим людям, погуглив этот вопрос;)


3
Мне нравится это решение, потому что вы можете ; commentисключить некоторые элементы из списка, не переписывая весь список.
Вим

1
+1, но если вы сделаете это, просто будьте осторожны с использованием key, так как ConfigParser преобразует все такие ключи в нижний регистр
Алекс Дин

4
@AlexDean Вы можете настроить ConfigParser, чтобы оставить camelCase на месте, установив optionxform = str. Пример: config = ConfigParser.SafeConfigParser() config.optionxform = str тогда дело будет оставлено в покое
Кэмерон Гудэйл

@ Генри Кук Вы проверяли это, когда ключ указан в списке несколько раз?
DevPlayer

1
@DevPlayer При использовании нескольких ключей вы получаете только последнее значение. (отвечает на 2-летний комментарий в пользу других читателей)
Marcin K

63

Многие люди не знают, что допускается использование многострочных значений конфигурации. Например:

;test.ini
[hello]
barlist = 
    item1
    item2

Значение config.get('hello','barlist')теперь будет:

"\nitem1\nitem2"

Которые вы легко можете разделить с помощью метода splitlines (не забудьте отфильтровать пустые элементы).

Если мы посмотрим на большой фреймворк, такой как Pyramid, они используют эту технику:

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

Источник

Сам, возможно, я бы расширил ConfigParser, если это обычная вещь для вас:

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

Обратите внимание, что есть несколько вещей, которые нужно учитывать при использовании этой техники

  1. Новые строки, которые являются элементами, должны начинаться с пробела (например, пробел или табуляция)
  2. Все последующие строки, начинающиеся с пробела, считаются частью предыдущего элемента. Также, если он имеет знак = или если он начинается с; следуя пробелу.

Почему вы используете .splitlines()вместо .split()? При использовании поведения каждого из них разделение явно лучше (отфильтровывает пустые строки). Если я что - то не хватает ...
rsaw

7
.split () разбивает все пробелы (если не указан конкретный символ), .splitlines () разбивает все символы новой строки.
Питер Смит

Аааа, хорошая мысль. Я не думал об этом, поскольку ни в одном из моих ценностей не было пробелов.
Рассмотрение

38

Если вы хотите буквально передать список, вы можете использовать:

ast.literal_eval()

Например, конфигурация:

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

Код является:

import ConfigParser
import ast

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

вывод:

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

В этом случае, в чем преимущество использования по ast.literal_eval()сравнению с использованием (возможно, более популярным) json.loads()? Я думаю, что последний обеспечивает большую безопасность, нет?
RayLuo

2
Я хотел бы видеть и пример этого, не стесняйтесь добавлять ответ в эту ветку, если считаете, что это поможет, хотя ваш комментарий сам по себе может стать хорошим вопросом. Ответ, который я дал, упрощает использование списков из ConfigParser, поэтому является внутренним приложением, устраняющим сложность использования регулярных выражений. Я не мог бы прокомментировать его значение "безопасности" без контекста.
PythonTester

Я был бы осторожен, используя literal_eval, который ожидает строку Python после = или: следовательно, вы не можете больше использовать, например, path1 = / some / path / but path1 = '/ some / path /'
vldbnc

21

Никакое упоминание о converterskwargConfigParser() в любом из этих ответов было довольно разочаровывающим.

Согласно документации вы можете передать словарь ConfigParser, который добавит getметод как для парсера, так и для прокси раздела. Итак, для списка:

example.ini

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

Пример парсера:

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

Это мой личный фаворит, так как нет необходимости создавать подклассы, и мне не нужно полагаться на конечного пользователя, чтобы идеально написать JSON или список, который может быть интерпретирован ast.literal_eval.


15

Я приземлился здесь в поисках этого ...

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

Ответ состоит в том, чтобы разделить его на запятую и убрать пробелы:

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

Чтобы получить список результатов:

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

Он может не отвечать на вопрос ОП точно, но может быть простым ответом, который ищут некоторые люди.


2
Я думал, что Дик был в sorger@espionage.su! Не удивительно, что моя почта продолжала подпрыгивать! > _ <
Августа

1
Читая этот комментарий 4 года спустя и посмеиваясь над пасхальным яйцом
любопытный инженер

11

Вот что я использую для списков:

содержимое файла конфигурации:

[sect]
alist = a
        b
        c

код:

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

это работает для строк

в случае чисел

содержание конфигурации:

nlist = 1
        2
        3

код:

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

Спасибо.


Это один , что я на самом деле ищу благодарность @LittleEaster
Ashley

5

Поэтому другой способ, который я предпочитаю, это просто разделить значения, например:

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

Может быть загружен следующим образом в список строк или целых чисел:

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

Этот метод избавляет вас от необходимости заключать значения в скобки для загрузки в формате JSON.


Привет Митч, в последнем случае не было бы лучше использовать get_int ('first_row'). Split (',') вместо явного преобразования его в int во время цикла?
Гвидо

2

Только примитивные типы поддерживаются для сериализации конфигурационным парсером. Я бы использовал JSON или YAML для такого рода требований.


спасибо за разъяснения, утку. единственная проблема в том, что я не могу использовать внешние пакеты в данный момент. я думаю, что напишу простой класс, чтобы справиться с этим. Я поделюсь этим в конце концов.
фисташка

Какую версию Python вы используете? Модуль JSON включен в 2.6.
Патрик Харрингтон

2

Я сталкивался с той же проблемой в прошлом. Если вам нужны более сложные списки, рассмотрите возможность создания собственного синтаксического анализатора путем наследования от ConfigParser. Затем вы переписали бы метод get следующим образом:

    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

С помощью этого решения вы также сможете определять словари в вашем конфигурационном файле.

Но будь осторожен! Это не так безопасно: это означает, что любой может запустить код через ваш конфигурационный файл. Если безопасность не является проблемой в вашем проекте, я хотел бы рассмотреть возможность использования непосредственно классов Python в качестве файлов конфигурации. Следующее является гораздо более мощным и затратным, чем файл ConfigParser:

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

Однако я думал об этом: почему бы не настроить значения конфигурации, как, barList=item1,item2а затем вызвать if value.find(',') > 0: return value.split(','), или, что еще лучше, приложение проанализировать все параметры конфигурации как списки, и только .split(',')все вслепую?
Дрооганс

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

Итак, теперь мой config.cfgфайл, который может выглядеть так:

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

Можно разобрать на достаточно мелкозернистые объекты для моего небольшого проекта.

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

Это делается для очень быстрого анализа простых конфигов: вы теряете возможность извлекать целые, логические и другие типы выходных данных без преобразования объекта, возвращаемого из Parser, или повторного выполнения работы по анализу, выполняемой классом Parser в другом месте.


1

Я выполнил аналогичную задачу в своем проекте с разделом с ключами без значений:

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)

Вывод:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn

0

json.loadsast.literal_evalКажется, & работает, но простой список в конфигурации обрабатывает каждый символ как байт, возвращая даже квадратную скобку ....

то есть если в конфиге есть fieldvalue = [1,2,3,4,5]

затем config.read(*.cfg) config['fieldValue'][0]возвращается [на место1


0

Как упомянул Питер Смит ( https://stackoverflow.com/a/11866695/7424596 ) Возможно, вы захотите расширить ConfigParser, кроме того, для автоматического преобразования в и из списка можно использовать Интерполятор.

Для справки внизу вы можете найти код, который автоматически преобразует конфигурацию, например

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

Поэтому, если вы запросите ключи, вы получите:

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

Код:

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 имейте в виду важность отступа. Как читается в строке документа ConfigParser:

Значения могут занимать несколько строк, если они имеют отступ глубже, чем первая строка значения. В зависимости от режима синтаксического анализатора, пустые строки могут рассматриваться как части многострочных значений или игнорироваться.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.