Listy w ConfigParser


Odpowiedzi:


142

Nic nie stoi na przeszkodzie, aby zapakować listę w rozdzielany ciąg znaków, a następnie rozpakować ją po otrzymaniu ciągu z konfiguracji. Gdybyś zrobił to w ten sposób, twoja sekcja konfiguracji wyglądałaby tak:

[Section 3]
barList=item1,item2

Nie jest ładna, ale działa w przypadku większości prostych list.


2
A jeśli masz złożone listy, możesz odnieść się do tego pytania: stackoverflow.com/questions/330900/… :-)
John Fouhy,

fajne rozwiązanie, ale jak to zrobić, jeśli nie ma możliwego ogranicznika, który możesz zagwarantować, nie pojawi się w pozycji listy ???
wim

@wim Zobacz moją odpowiedź, możesz użyć \ n jako separatora
Peter Smit

@wim Musisz zaimplementować sposób na uniknięcie znaku ogranicznika, jeśli może to być znak prawny. (I sposób na ucieczkę przed jakąkolwiek postacią, której używasz do ucieczki.)
jamesdlin

A jeśli lista zawiera jeden element?
Sérgio Mafra

223

Trochę późno, ale dla niektórych może pomóc. Używam kombinacji ConfigParser i JSON:

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

po prostu przeczytaj:

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

Możesz nawet łamać linie, jeśli lista jest długa (dzięki @ peter-smit):

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

Oczywiście mógłbym po prostu użyć JSON, ale uważam, że pliki konfiguracyjne są dużo bardziej czytelne, a sekcja [DEFAULT] jest bardzo przydatna.


1
Jest niesamowity, ponieważ automatycznie „rzuca” wartości, co może być przydatne, jeśli nie znasz wcześniej typów.
LeGBT

Uwielbiam ten pomysł, ale mogę go uruchomić tylko z listami liczb. Cudzysłowy nie pomagają. Dziwne. Iść dalej.
rsaw

5
Będziesz musiał mieć ["a", "b", "c"], aby napisy działały. Dla mnie to kliknięcia dla liczb, ale ponieważ pliki cfg są w większości edytowalne - dodawanie „” za każdym razem jest uciążliwe. Wolałbym użyć przecinka, a następnie go podzielić.
Saurabh Hirani

Eleganckie rozwiązanie wykorzystujące tylko standardową bibliotekę. Miło móc używać komentarzy i json.
wi1

jak to działałoby w przypadku nieprzetworzonych ciągów, np. key5 : [r"abc $x_i$", r"def $y_j$"]? Podnoszą błądjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu

101

Spóźniłem się na tę imprezę, ale niedawno zaimplementowałem to z dedykowaną sekcją w pliku konfiguracyjnym dla listy:

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

i używając, config.items( "paths" )aby uzyskać iterowalną listę elementów ścieżki, na przykład:

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

Mam nadzieję, że to pomoże innym osobom szukającym w Google tego pytania;)


3
Podoba mi się to rozwiązanie, ponieważ można ; commentwypisać określone pozycje z listy bez konieczności przepisywania całej listy.
wim

1
+1, ale jeśli to zrobisz, po prostu uważaj, używając również key, ponieważ ConfigParser konwertuje wszystkie takie klucze na małe litery
Alex Dean

4
@AlexDean Możesz ustawić ConfigParser, aby pozostawić camelCase na miejscu, ustawiając optionxform = str. Przykład: config = ConfigParser.SafeConfigParser() config.optionxform = str Wtedy sprawa zostanie sama
Cameron Goodale

@Henry Cooke Czy przetestowałeś to, gdy klucz jest wymieniony wiele razy?
DevPlayer

1
@DevPlayer Przy użyciu wielu klawiszy otrzymujesz tylko ostatnią wartość. (odpowiadając na komentarz sprzed 2 lat dla innych czytelników)
Marcin K

63

Wiele osób nie wie, że dozwolone są wieloliniowe wartości konfiguracyjne. Na przykład:

;test.ini
[hello]
barlist = 
    item1
    item2

Wartość config.get('hello','barlist')will będzie teraz wynosić:

"\nitem1\nitem2"

Które możesz łatwo podzielić metodą podziału linii (nie zapomnij filtrować pustych elementów).

Jeśli spojrzymy na duże ramy, takie jak Pyramid, używają tej techniki:

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

Źródło

Osobiście może rozszerzyłbym ConfigParser, jeśli jest to typowe dla Ciebie:

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

Zauważ, że jest kilka rzeczy, na które należy zwrócić uwagę podczas korzystania z tej techniki

  1. Nowe wiersze będące elementami powinny zaczynać się od białych znaków (np. Spacja lub tabulator)
  2. Wszystkie następne wiersze zaczynające się od białych znaków są traktowane jako część poprzedniego elementu. Również jeśli ma znak = lub zaczyna się od; po spacji.

Dlaczego używasz .splitlines()zamiast .split()? Używając domyślnego zachowania każdego z nich, split jest wyraźnie lepszy (filtruje puste wiersze). Chyba że czegoś mi brakuje ...
rsaw

7
.split () przerywa na wszystkich białych znakach (chyba że podano konkretny znak), .splitlines () przerywa na wszystkich znakach nowej linii.
Peter Smit

Ach, dobra uwaga. Nie myślałem o tym, ponieważ żadna z moich wartości nie miała spacji.
rsaw

38

Jeśli chcesz dosłownie przekazać listę, możesz użyć:

ast.literal_eval()

Na przykład konfiguracja:

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

Kod to:

import ConfigParser
import ast

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

wynik:

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

W tym przypadku, jaka jest zaleta używania w ast.literal_eval()porównaniu do używania (prawdopodobnie bardziej popularnego) json.loads()? Myślę, że to drugie zapewnia większe bezpieczeństwo, prawda?
RayLuo

2
Bardzo chciałbym zobaczyć i przykład tego, nie krępuj się dodać odpowiedź do tego wątku, jeśli uważasz, że to pomogłoby, chociaż twój komentarz sam w sobie byłby dobrym pytaniem. Odpowiedź, której udzieliłem, upraszcza korzystanie z list z ConfigParser, więc jest wewnętrzna dla aplikacji, usuwając komplikację korzystania z wyrażenia regularnego. Nie mogłem wypowiedzieć się na temat jego „bezpieczeństwa” bez kontekstu.
PythonTester

Byłbym ostrożny używając literal_eval, które oczekują łańcucha pythona po = lub: stąd nie możesz już używać, np. Path1 = / some / path / ale path1 = '/ some / path /'
vldbnc

21

Żadna wzmianka o converterskwargConfigParser() w żadnej z tych odpowiedzi była raczej rozczarowująca.

Zgodnie z dokumentacją możesz przekazać słownik ConfigParser, który doda getmetodę zarówno dla parsera, jak i dla serwerów proxy sekcji. Więc dla listy:

example.ini

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

Przykład parsera:

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

Jest to mój osobisty faworyt, ponieważ żadne podklasy nie są potrzebne i nie muszę polegać na użytkowniku końcowym, aby doskonale napisał JSON lub listę, którą można zinterpretować ast.literal_eval.


15

Wylądowałem tutaj, chcąc skonsumować to ...

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

Odpowiedzią jest podzielenie go na przecinek i usunięcie spacji:

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

Aby uzyskać wynik listy:

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

Może nie odpowiadać dokładnie na pytanie PO, ale może być prostą odpowiedzią, której niektórzy ludzie szukają.


2
Myślałem, że Dick jest na sorger@espionage.su! Nic dziwnego, że moja poczta odbijała się dalej! > _ <
Augusta

1
Czytając ten komentarz 4 lata później i chichocząc z pisanki
ciekawy inżynier

11

Oto, czego używam do list:

zawartość pliku konfiguracyjnego:

[sect]
alist = a
        b
        c

kod :

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

działa na strunach

w przypadku liczb

zawartość konfiguracji:

nlist = 1
        2
        3

kod:

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

dzięki.


To jest ten, którego tak naprawdę szukałem, dzięki @LittleEaster
ashley

5

Więc innym sposobem, który wolę, jest po prostu podzielenie wartości, na przykład:

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

Można go załadować w ten sposób do listy ciągów lub liczb całkowitych, w następujący sposób:

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

Ta metoda zapobiega konieczności zawijania wartości w nawiasach, aby załadować je jako JSON.


Cześć Mitch, w tym drugim przypadku nie byłoby przyjemniej użyć get_int ('first_row'). Split (',') zamiast jawnie konwertować go na int podczas pętli?
Guido,

2

W przypadku serializacji przez analizator konfiguracji obsługiwane są tylko typy pierwotne. Używałbym JSON lub YAML do tego rodzaju wymagań.


dzięki za wyjaśnienie, utku. jedynym problemem jest to, że w tej chwili nie mogę korzystać z zewnętrznych pakietów. Myślę, że napiszę prostą klasę, która sobie z tym poradzi. w końcu się tym podzielę.
pistacchio

Jakiej wersji Pythona używasz? Moduł JSON jest zawarty w 2.6.
Patrick Harrington,

2

W przeszłości miałem ten sam problem. Jeśli potrzebujesz bardziej złożonych list, rozważ utworzenie własnego parsera poprzez dziedziczenie po ConfigParser. Następnie nadpisałbyś metodę get tym:

    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

Dzięki temu rozwiązaniu będziesz mógł również zdefiniować słowniki w swoim pliku konfiguracyjnym.

Ale bądź ostrożny! To nie jest tak bezpieczne: oznacza to, że każdy może uruchomić kod za pośrednictwem twojego pliku konfiguracyjnego. Jeśli bezpieczeństwo nie jest problemem w twoim projekcie, rozważę użycie bezpośrednio klas Pythona jako plików konfiguracyjnych. Poniższy plik jest znacznie bardziej wydajny i można go stracić niż plik ConfigParser:

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

Myślałem jednak o zrobieniu tego: dlaczego nie ustawić wartości konfiguracyjnych, takich jak, barList=item1,item2a następnie wywołać if value.find(',') > 0: return value.split(','), lub jeszcze lepiej, pozwolić aplikacji przeanalizować wszystkie opcje konfiguracyjne jako listy i po prostu .split(',')wszystko na ślepo?
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(',')

Więc teraz mój config.cfgplik, który mógłby wyglądać tak:

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

Może zostać przetworzony na drobnoziarniste obiekty - wystarczająco dla mojego małego projektu.

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

Służy to bardzo szybkiemu analizowaniu prostych konfiguracji, tracisz możliwość pobierania int, bools i innych typów danych wyjściowych bez przekształcania obiektu zwróconego z Parserlub ponownego wykonywania zadania przetwarzania wykonanego przez klasę Parser w innym miejscu.


1

Wykonałem podobne zadanie w moim projekcie z sekcją z kluczami bez wartości:

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)

Wynik:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn

0

json.loads & ast.literal_eval wydaje się działać, ale prosta lista ciągu config traktuje każdy znak jako bajt więc wracając nawet nawias kwadratowy ....

co oznacza, że ​​config ma fieldvalue = [1,2,3,4,5]

następnie config.read(*.cfg) config['fieldValue'][0]wracając [w miejsce1


0

Jak wspomniał Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ) Możesz chcieć rozszerzyć ConfigParser, a ponadto można użyć Interpolatora do automatycznej konwersji na iz listy.

Dla odniesienia na dole możesz znaleźć kod, który automatycznie konwertuje konfigurację, taki jak:

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

Więc jeśli poprosisz o klucze, otrzymasz:

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

Kod:

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 pamiętaj o znaczeniu wcięcia. Jak czyta się w ciągu dokumentacji programu ConfigParser:

Wartości mogą obejmować wiele wierszy, o ile są wcięte głębiej niż pierwszy wiersz wartości. W zależności od trybu parsera, puste wiersze mogą być traktowane jako części wartości wielowierszowych lub ignorowane.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.