Списки в ConfigParser


Відповіді:


142

Ніщо не заважає вам упакувати список у розділену рядок, а потім розпакувати його, як тільки ви отримаєте рядок із конфігурації. Якщо ви зробили це так, ваш розділ конфігурації виглядатиме так:

[Section 3]
barList=item1,item2

Це не дуже, але функціонально для більшості простих списків.


2
І якщо у вас є складні списки, ви можете звернутися до цього питання: stackoverflow.com/questions/330900 / ... :-)
Джон Fouhy

приємне рішення, але як це зробити, якщо немає можливого роздільника, ніж ви можете гарантувати, не з’явиться всередині елемента списку ???
Вім

@wim Дивіться мою відповідь, ви можете використовувати \ n як роздільник
Peter Smit

@wim Вам потрібно буде реалізувати спосіб уникнути символу роздільника, якщо він може мати юридичний характер. (І спосіб уникнути будь-якого персонажа, який ви використовуєте для втечі.)
jamesdlin

Що робити, якщо у списку є один елемент?
Мафра

223

Також трохи пізно, але може бути корисним для деяких. Я використовую комбінацію 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

Мені подобається ця ідея, але я можу змусити її працювати лише зі списками номерів. Лапки не допомагають. Дивно. Жити далі.
rsaw

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

Елегантне рішення з використанням лише стандартної бібліотеки. Приємно, що можна використовувати коментарі та json.
wi1

як це буде працювати для сирих рядків, наприклад 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 так, щоб залишити камелюCase на місці, встановивши optionxform = str. Приклад: config = ConfigParser.SafeConfigParser() config.optionxform = str Тоді справа залишиться в спокої
Камерон Гудале

@Henry Cooke Ви перевіряли це, коли ключ перераховується кілька разів?
DevPlayer

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

63

Дуже багато людей не знають, це те, що багаторядкові значення конфігурації дозволені. Наприклад:

;test.ini
[hello]
barlist = 
    item1
    item2

Тепер значення config.get('hello','barlist')буде:

"\nitem1\nitem2"

Який ви легко можете розділити методом splitlines (не забудьте відфільтрувати порожні елементи).

Якщо ми подивимось на такі великі рамки, як Піраміда, вони використовують цю техніку:

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 () перерви на всі символи нового рядка.
Пітер Сміт

Ах, хороший момент. Я не думав про це, оскільки жодна з моїх цінностей не мала пробілів.
rsaw

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

Жодна згадка про 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.


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(',')все наосліп?
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(',')

Отже, тепер мій 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'

Це для дуже швидкого розбору простих конфігурацій, ви втрачаєте всю здатність отримувати вставки, bools та інші типи виводу, не трансформуючи повернений об'єкт 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.loads&, ast.literal_evalздається, працює, але простий список у config розглядає кожен символ як байт, тому повертає рівну квадратну дужку ....

тобто якщо config має 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 пам’ятайте про важливість відступу. Як читається в рядку doc ConfigParser:

Значення можуть охоплювати кілька рядків, якщо вони з відступом глибше першого рядка значення. Залежно від режиму аналізатора, порожні рядки можуть розглядатися як частини багаторядкових значень або ігноруватися.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.