Как 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

or

['value1', 'value2']
['yyy']

(Я не возражаю, если КАЖДОЕ значение находится в списке...). Любые предложения приветствуются.


person Alex    schedule 06.04.2013    source источник


Ответы (5)


После небольшой модификации мне удалось добиться желаемого:

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']
person Nathan Villaescusa    schedule 06.04.2013
comment
Я понятия не имею, почему super(OrderedDict, self) работает, а super(MultiOrderedDict, self) нет. - person Nathan Villaescusa; 06.04.2013
comment
Да, идеально! Кроме super аномалии. Может быть, это является базовым классом OrderedDict, тогда... - person Alex; 06.04.2013
comment
Есть ли способ заставить это возвращать одно значение вместо списка, когда есть только одно значение? ['value1', 'value2'] и yyy вместо ['yyy'] - person Vangelis Tasoulas; 20.02.2014
comment
У вас есть способ заставить это работать в python 3? Я получаю сообщение об ошибке: configparser.DuplicateOptionError, и я действительно не знаю, что делаю. - person bgStack15; 22.07.2016
comment
@bgStack15 вы должны установить strict на False, например: config = configparser.RawConfigParser(dict_type=MultiOrderedDict, strict=False) - person Roberto; 20.10.2016
comment
Кажется, это возвращает значения с символом новой строки вместо массива для 2.6/2.7. - person Sarkie; 04.09.2018
comment
Я использую 3.7, и я тоже получаю строку, разделенную '\n', похоже, это связано с возвращаемым configparser.SectionProxy. - person Melendowski; 27.07.2020

Принятый ответ нарушает 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
person Stefan Bohlein    schedule 15.08.2017
comment
Я исправил неправильный вызов super() в принятом ответе, и теперь section() работает. - person bernie; 25.10.2017

в python 3.8 вам также нужно добавить strict=False:

class MultiOrderedDict(OrderedDict):
    def __setitem__(self, key, value):
        if isinstance(value, list) and key in self:
            self[key].extend(value)
        else:
            super().__setitem__(key, value)

config = ConfigParser.RawConfigParser(dict_type=MultiOrderedDict, strict=False)
config.read(['a.txt'])
print config.get("test",  "foo")
print config.get("test",  "xxx")
person Ali    schedule 22.04.2020
comment
Благодарю вас! Это исправило мои проблемы в Python 3.7.4. - person nimig18; 31.01.2021

Дополнительные примеры Несколько значений в 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 перед преобразованием всегда является списком или словарем (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')]
person user2185338    schedule 09.07.2016

Просто немного измените ответ на @abarnert, иначе он вызывает __setitem__ рекурсивно и по какой-то причине не остановится.

ини-файл:

[section]
key1   = value1
key2[] = value21
key2[] = value22

Питон:

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'])
person user1633272    schedule 08.05.2017