Списки в ConfigParser
Типичный сгенерированный файл ConfigParser выглядит так:
[Section]
bar=foo
[Section 2]
bar2= baz
Теперь, есть ли способ индексировать списки, например, например:
[Section 3]
barList={
item1,
item2
}
Смежный вопрос: уникальные ключи Python ConfigParser для каждого раздела
19 ответов
Ничто не мешает вам упаковать список в строку с разделителями, а затем распаковать его, как только вы получите строку из конфигурации. Если бы вы сделали это таким образом, ваш раздел конфигурации будет выглядеть так:
[Section 3]
barList=item1,item2
Это не красиво, но это функционально для большинства простых списков.
Также немного поздно, но может быть полезно для некоторых. Я использую комбинацию 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] очень удобным.
Опаздываю на эту вечеринку, но недавно я реализовал это с помощью специального раздела в файле конфигурации для списка:
[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
Надеюсь, это поможет другим людям, погуглив этот вопрос;)
Многие люди не знают, что допускается использование многострочных значений конфигурации. Например:
;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)]
Обратите внимание, что есть несколько вещей, которые нужно учитывать при использовании этой техники
- Новые строки, которые являются элементами, должны начинаться с пробела (например, пробел или табуляция)
- Все последующие строки, начинающиеся с пробела, считаются частью предыдущего элемента. Также, если он имеет знак = или если он начинается с; следуя пробелу.
Нет упоминания о converters
карг дляConfigParser()
в любом из этих ответов было довольно неутешительно.
Согласно документации, вы можете передать словарь 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
,
Если вы хотите буквально передать список, вы можете использовать:
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"]
Я приземлился здесь в поисках этого...
[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']
Он может не отвечать на вопрос ОП точно, но может быть простым ответом, который ищут некоторые люди.
Вот что я использую для списков:
содержимое файла конфигурации:
[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]
Благодарю.
Другой способ, который я предпочитаю, - просто разделить значения, например:
#/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.
Аналогичную задачу я выполнил в своем проекте с разделом с ключами без значений:
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
Только примитивные типы поддерживаются для сериализации конфигурационным парсером. Я бы использовал JSON или YAML для такого рода требований.
Чтобы продолжить ответ Grr (мой любимый), вместо того, чтобы заключать элементы списка в кавычки в файле .ini, вы можете использовать функцию карты. Это позволяет вам питонически указывать типы данных элемента списка.
Файл конфигурации:
[section]
listKey1: 1001, 1002, 1003
listKey2: AAAA, BBBB, CCCC
Код:
cfgFile = 'config.ini'
parser = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]})
parser.read(cfgFile)
list1 = list(map(int, parser.getlist('section', 'listKey1')))
list2 = list(map(str, parser.getlist('section', 'listKey2')))
print(list1)
print(list2)
Выход:
[1001, 1002, 1003]
['AAAA', 'BBBB', 'CCCC']
Если это ваш config.ini:
[Section 3]
barList=item1,item2
Затем с помощью configparser вы можете сделать это:
from configparser import ConfigParser
config = ConfigParser()
config.read('config.ini')
my_list = config['Section 3']['barList'].split(',')
Ты получишь:
my_list = ['item1', 'item2']
Метод split() возвращает список, см. документацию по строкам Python.
Если у вас есть пробелы в вашем config.ini, например:
[Section 3]
barList= item1, item2
Тогда вам лучше сделать это:
my_list = [x.strip() for x in config['Section 3']['barList'].split(',')]
Если ваши элементы являются числами (например, целыми числами), просто примените:
my_list_of_ints = list(map(int, my_list))
Ты получишь:
my_list_of_ints = [item1, item2]
Я сталкивался с той же проблемой в прошлом. Если вам нужны более сложные списки, рассмотрите возможность создания собственного синтаксического анализатора путем наследования от 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 ]
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'
Это для очень быстрого разбора простых конфигов, вы теряете возможность извлекать целочисленные значения, значения типа bool и другие типы вывода без преобразования объекта, возвращаемого из Parser
или повторное выполнение анализа, выполняемого классом Parser в другом месте.
Улучшение функции split(',') может состоять в том, чтобы обрабатывать значения, разделенные запятыми, как запись в файле CSV.
import csv
my_list = list(csv.reader([config['Section 3']['barList']], dialect=csv.excel))[0]
Вы можете настроить диалект для анализа любого стиля CSV, который вам нравится.
json.loads & ast.literal_eval, кажется, работает, но простой список в конфигурации обрабатывает каждый символ как байт, возвращая даже квадратную скобку....
то есть если в config есть значение поля = [1,2,3,4,5]
затем config.read(*.cfg) config['fieldValue'][0], возвращая "[" вместо 1
вы можете использовать список в файле конфигурации, а затем проанализировать его в python
from ast import literal_eval
literal_eval("[1,2,3,4]")
import json
json.loads("[1,2,3,4]")
а также вы можете использовать файл json позади своего файла конфигурации следующим образом:
your config file :
[A]
json_dis = .example.jason
--------------------
your code :
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
# getting items of section A
config.items('A')
# result is a list of key-values
Как упоминал Питер Смит ( /questions/47858607/spiski-v-configparser/47858649#47858649), вы можете захотеть расширить ConfigParser, кроме того, можно использовать Interpolator для автоматического преобразования в список и из него.
Для справки внизу вы можете найти код, который автоматически преобразует конфигурацию, например:
[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:
Значения могут охватывать несколько строк, если они имеют отступ глубже, чем первая строка значения. В зависимости от режима анализатора пустые строки могут рассматриваться как части многострочных значений или игнорироваться.