Как ConfigParse файл, сохраняющий несколько значений для идентичных ключей?
Мне нужно иметь возможность использовать ConfigParser
для чтения нескольких значений для одного и того же ключа. Пример файла конфигурации:
[test]
foo = value1
foo = value2
xxx = yyy
При "стандартном" использовании ConfigParser
будет один ключ foo
со значением value2
. Но мне нужен синтаксический анализатор для чтения в обоих значениях.
После записи в дубликатном ключе я создал следующий пример кода:
from collections import OrderedDict
from ConfigParser import RawConfigParser
class OrderedMultisetDict(OrderedDict):
def __setitem__(self, key, value):
try:
item = self.__getitem__(key)
except KeyError:
super(OrderedMultisetDict, self).__setitem__(key, value)
return
print "item: ", item, value
if isinstance(value, list):
item.extend(value)
else:
item.append(value)
super(OrderedMultisetDict, self).__setitem__(key, item)
config = RawConfigParser(dict_type = OrderedDict)
config.read(["test.cfg"])
print config.get("test", "foo")
print config.get("test", "xxx")
config2 = RawConfigParser(dict_type = OrderedMultisetDict)
config2.read(["test.cfg"])
print config2.get("test", "foo")
print config.get("test", "xxx")
Первая часть (с config
) читается в файле конфигурации "обычный", оставив только value2
в качестве значения для foo
(перезаписывая/удаляя другое значение), и я получаю следующий ожидаемый вывод
value2
yyy
Вторая часть (config2
) использует мой подход для добавления нескольких значений в список, но вместо этого вывод
['value1', 'value2', 'value1\nvalue2']
['yyy', 'yyy']
Как избавиться от повторяющихся значений? Я ожидаю выход следующим образом:
['value1', 'value2']
yyy
или
['value1', 'value2']
['yyy']
(Я не возражаю, если значение EVERY находится в списке...). Любые предложения приветствуются.
Ответы
Ответ 1
После небольшой модификации я смог добиться того, чего вы хотите:
class MultiOrderedDict(OrderedDict):
def __setitem__(self, key, value):
if isinstance(value, list) and key in self:
self[key].extend(value)
else:
super(MultiOrderedDict, self).__setitem__(key, value)
# super().__setitem__(key, value) in Python 3
config = ConfigParser.RawConfigParser(dict_type=MultiOrderedDict)
config.read(['a.txt'])
print config.get("test", "foo")
print config.get("test", "xxx")
Выходы:
['value1', 'value2']
['yyy']
Ответ 2
Принятый ответ прерывается config.sections()
, он всегда возвращает пустой список (тестируется с Python 3.5.3). Замена super(OrderedDict, self).__setitem__(key, value)
на super().__setitem__(key, value)
исправляет это, но теперь config.get(section, key)
возвращает конкатенированную строку, больше не список строк.
Мое решение:
class ConfigParserMultiValues(collections.OrderedDict):
def __setitem__(self, key, value):
if key in self and isinstance(value, list):
self[key].extend(value)
else:
super().__setitem__(key, value)
@staticmethod
def getlist(value):
return value.split(os.linesep)
config = configparser.ConfigParser(strict=False, empty_lines_in_values=False, dict_type=ConfigParserMultiValues, converters={"list": ConfigParserMultiValues.getlist})
...
values = config.getlist("Section", "key") # => ["value1", "value2"]
Файл конфигурации INI принимает повторяющиеся ключи:
[Section]
key = value1
key = value2
Ответ 3
Немного измените на @abarnert answer, в противном случае он вызывает рекурсивно __setitem__ и по какой-то причине не остановится.
ini файл:
[section]
key1 = value1
key2[] = value21
key2[] = value22
Python:
class MultiOrderedDict(OrderedDict):
LIST_SUFFIX = '[]'
LIST_SUFFIX_LEN = len(LIST_SUFFIX)
def __setitem__(self, key, value):
if key.endswith(self.LIST_SUFFIX):
values = super(OrderedDict, self).setdefault(key, [])
if isinstance(value, list):
values.extend(value)
else:
values.append(value)
else:
super(MultiOrderedDict, self).__setitem__(key, value)
def __getitem__(self, key):
value = super(MultiOrderedDict, self).__getitem__(key)
if key.endswith(self.LIST_SUFFIX) and not isinstance(value, list):
value = value.split('\n')
return value
Тест:
def test_ini(self):
dir_path = os.path.dirname(os.path.realpath(__file__))
config = RawConfigParser(dict_type=MultiOrderedDict, strict=False)
config.readfp(codecs.open('{}/../config/sample.ini'.format(dir_path), encoding="utf_8_sig"))
self.assertEquals(config.get("section1", "key1"), 'value1')
self.assertEquals(config.get("section1", "key2[]"), ['value21', 'value22'])
Ответ 4
Дополнительные примеры Несколько значений в test.cfg
.
[test]
foo = value1
foo = value2
value3
xxx = yyy
<whitespace>value3
добавить value3
в список foo.
ConfigParser
преобразует список в строку.
/usr/lib/python2.7/ConfigParser.pyc in _read(self, fp, fpname)
552 for name, val in options.items():
553 if isinstance(val, list):
--> 554 options[name] = '\n'.join(val)
555
value
перед преобразованием всегда является списком или dict (MultiOrderedDict
).
Попробуйте это - с ним, config.items
работает:
from collections import OrderedDict
import ConfigParser
class MultiOrderedDict(OrderedDict):
def __setitem__(self, key, value):
if key in self:
if isinstance(value, list):
self[key].extend(value)
return
elif isinstance(value,str):
return # ignore conversion list to string (line 554)
super(MultiOrderedDict, self).__setitem__(key, value)
config = ConfigParser.RawConfigParser(dict_type=MultiOrderedDict)
config.read(['test.cfg'])
print config.get("test", "foo")
print config.get("test", "xxx")
print config.items("test")
Выходы:
['value1', 'value2', 'value3']
['yyy']
[('foo', ['value1', 'value2', 'value3']), ('xxx', ['yyy'])]
Другая реализация MultiOrderedDict
class MultiOrderedDict(OrderedDict):
def __setitem__(self, key, value):
if key in self:
if isinstance(value, list):
self[key].extend(value)
return
elif isinstance(value,str):
if len(self[key])>1:
return
super(MultiOrderedDict, self).__setitem__(key, value)
Выходы:
['value1', 'value2', 'value3']
yyy
[('foo', ['value1', 'value2', 'value3']), ('xxx', 'yyy')]