Daftar di ConfigParser

182

File khas ConfigParser yang dihasilkan terlihat seperti:

[Section]
bar=foo
[Section 2]
bar2= baz

Sekarang, apakah ada cara untuk mengindeks daftar seperti, misalnya:

[Section 3]
barList={
    item1,
    item2
}

Pertanyaan terkait: Kunci unik Python ConfigParser per bagian

pistacchio
sumber

Jawaban:

142

Tidak ada yang menghentikan Anda dari mengemas daftar menjadi string yang dibatasi dan kemudian membukanya setelah Anda mendapatkan string dari konfigurasi. Jika Anda melakukannya dengan cara ini bagian konfigurasi Anda akan terlihat seperti:

[Section 3]
barList=item1,item2

Itu tidak cantik tetapi fungsional untuk daftar paling sederhana.

David Locke
sumber
2
Dan jika Anda memiliki daftar yang rumit, Anda dapat merujuk ke pertanyaan ini: stackoverflow.com/questions/330900/... :-)
John Fouhy
solusi yang bagus, tetapi bagaimana cara melakukannya jika tidak ada pembatas yang mungkin Anda dapat menjamin tidak akan muncul di dalam daftar barang ???
wim
@wim Lihat jawaban saya, Anda dapat menggunakan \ n sebagai pembatas
Peter Smit
@wim Anda perlu menerapkan cara untuk keluar dari karakter pembatas jika itu bisa menjadi karakter hukum. (Dan cara untuk melarikan diri karakter apa pun yang Anda gunakan untuk melarikan diri.)
jamesdlin
Bagaimana jika daftar memiliki elemen tunggal?
Sérgio Mafra
223

Juga agak terlambat, tetapi mungkin bermanfaat bagi sebagian orang. Saya menggunakan kombinasi ConfigParser dan JSON:

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

baca saja dengan:

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

Anda bahkan dapat memutus garis jika daftar Anda panjang (terima kasih @ peter-smit):

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

Tentu saja saya bisa menggunakan JSON, tetapi saya menemukan file konfigurasi jauh lebih mudah dibaca, dan Bagian [DEFAULT] sangat berguna.

quasimodo
sumber
1
Ini luar biasa karena secara otomatis "melemparkan" nilai-nilai yang dapat berguna jika Anda tidak tahu jenisnya sebelumnya.
LeGBT
Saya suka ide ini, tetapi saya hanya bisa membuatnya bekerja dengan daftar angka. Tanda kutip tidak membantu. Aneh. Bergerak.
rsaw
5
Anda harus memiliki ["a", "b", "c"] agar string dapat digunakan. Bagi saya, ini klik untuk angka tetapi karena file cfg sebagian besar dapat diedit - menambahkan "" setiap kali terasa menyebalkan. Saya lebih suka menggunakan koma dan membaginya.
Saurabh Hirani
Solusi elegan hanya menggunakan perpustakaan standar. Senang bisa menggunakan komentar dan json.
wi1
bagaimana ini akan bekerja untuk string mentah, misalnya key5 : [r"abc $x_i$", r"def $y_j$"]? Mereka memunculkan kesalahanjson.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
kingusiu
101

Datang terlambat ke pesta ini, tapi saya baru-baru ini menerapkan ini dengan bagian khusus dalam file konfigurasi untuk daftar:

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

dan gunakan config.items( "paths" )untuk mendapatkan daftar item jalur yang dapat diubah, seperti:

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

Semoga ini bisa membantu orang lain Googling pertanyaan ini;)

Henry Cooke
sumber
3
Saya suka solusi ini, karena Anda dapat ; commentmengeluarkan item tertentu dari daftar tanpa harus menulis ulang seluruh daftar.
wim
1
+1, tetapi jika Anda melakukan ini, berhati-hatilah dengan menggunakan key, karena ConfigParser mengonversi semua kunci tersebut menjadi huruf kecil
Alex Dean
4
@AlexDean Anda dapat mengatur ConfigParser untuk membiarkan camelCase di tempatnya dengan mengatur optionxform = str. Contoh: config = ConfigParser.SafeConfigParser() config.optionxform = str Kemudian
kasing
@Henry Cooke Sudahkah Anda menguji bahwa ketika kunci terdaftar beberapa kali?
DevPlayer
1
@DevPlayer Dengan penggunaan multi-kunci Anda hanya mendapatkan nilai terakhir. (menanggapi komentar lama 2 tahun untuk kepentingan pembaca lain)
Marcin K
63

Satu hal yang banyak orang tidak tahu adalah bahwa nilai konfigurasi multi-line diizinkan. Sebagai contoh:

;test.ini
[hello]
barlist = 
    item1
    item2

Nilai config.get('hello','barlist')sekarang akan menjadi:

"\nitem1\nitem2"

Yang mudah Anda pisahkan dengan metode splitlines (jangan lupa untuk memfilter item kosong).

Jika kita melihat kerangka besar seperti Piramida, mereka menggunakan teknik ini:

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

Sumber

Saya sendiri, saya mungkin akan memperpanjang ConfigParser jika ini adalah hal yang umum untuk Anda:

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

Perhatikan bahwa ada beberapa hal yang harus diperhatikan ketika menggunakan teknik ini

  1. Baris baru yang merupakan item harus dimulai dengan spasi putih (mis. Spasi atau tab)
  2. Semua baris berikut yang dimulai dengan spasi dianggap sebagai bagian dari item sebelumnya. Juga jika memiliki tanda = atau jika dimulai dengan; mengikuti spasi putih.
Peter Smit
sumber
Mengapa Anda menggunakan .splitlines()bukan .split()? Menggunakan perilaku default masing-masing, split jelas lebih unggul (menyaring garis kosong). Kecuali saya kehilangan sesuatu ...
rsaw
7
.split () memecah semua spasi putih (kecuali karakter tertentu diberikan), .splitlines () memecah semua karakter baris baru.
Peter Smit
Poin bagus ahhh. Saya tidak memikirkan hal itu karena tidak ada nilai saya yang memiliki ruang.
rsaw
38

Jika Anda ingin benar - benar lulus dalam daftar maka Anda dapat menggunakan:

ast.literal_eval()

Sebagai contoh konfigurasi:

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

Kode tersebut adalah:

import ConfigParser
import ast

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

keluaran:

<type'list'>
["item1","item2","item3"]
PythonTester
sumber
Dalam hal ini, apa keuntungan menggunakan ast.literal_eval()ketika membandingkan dengan menggunakan (bisa dibilang lebih populer) json.loads()? Saya pikir yang terakhir memberikan lebih banyak keamanan, bukan?
RayLuo
2
Saya akan senang melihat dan memberikan contoh tentang ini, jangan ragu untuk menambahkan jawaban ke utas ini jika Anda merasa itu akan membantu, meskipun komentar Anda akan membuat pertanyaan yang bagus. Jawaban yang saya berikan menyederhanakan konsumsi daftar dari ConfigParser sehingga internal untuk aplikasi menghapus comlication menggunakan regex. Saya tidak bisa mengomentari nilai "keamanan" tanpa konteks.
PythonTester
Saya akan berhati-hati menggunakan literal_eval yang mengharapkan string python setelah = atau: maka Anda tidak dapat menggunakan lagi misalnya path1 = / some / path / but path1 = '/ some / path /'
vldbnc
21

Tidak disebutkan converterskwarg untukConfigParser() jawaban ini agak mengecewakan.

Menurut dokumentasi Anda dapat melewati kamus ConfigParseryang akan menambahkan getmetode untuk parser dan bagian proxy. Jadi untuk daftar:

contoh.ini

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

Contoh Parser:

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

Ini adalah favorit pribadi saya karena tidak diperlukan subklasifikasi dan saya tidak harus bergantung pada pengguna akhir untuk menulis JSON atau daftar yang bisa ditafsirkan dengan sempurna ast.literal_eval.

Grr
sumber
15

Saya mendarat di sini mencari untuk mengkonsumsi ini ...

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

Jawabannya adalah membaginya di koma dan menghapus spasi:

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

Untuk mendapatkan hasil daftar:

['[email protected]', '[email protected]']

Ini mungkin tidak menjawab pertanyaan OP dengan tepat tetapi mungkin jawaban sederhana yang dicari beberapa orang.

John Mee
sumber
2
Saya pikir Dick ada di [email protected]! Tidak heran email saya terus melambung! > _ <
Augusta
1
Membaca komentar ini 4 tahun kemudian dan tertawa kecil pada telur paskah
seorang insinyur yang ingin tahu
11

Inilah yang saya gunakan untuk daftar:

konten file konfigurasi:

[sect]
alist = a
        b
        c

kode:

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

ini bekerja untuk string

dalam hal angka

konten konfigurasi:

nlist = 1
        2
        3

kode:

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

Terima kasih.

LittleEaster
sumber
Ini adalah yang saya cari sebenarnya terima kasih @LittleEaster
ashley
5

Jadi cara lain, yang saya sukai, adalah dengan hanya membagi nilai-nilai, misalnya:

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

Dapat dimuat seperti ini ke daftar string atau integer, sebagai berikut:

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

Metode ini mencegah Anda dari perlu membungkus nilai-nilai Anda dalam tanda kurung untuk memuat sebagai JSON.

Mitch Gates
sumber
Hai Mitch, dalam kasus terakhir tidak akan lebih baik untuk menggunakan get_int ('first_row'). Split (',') alih-alih secara eksplisit mengubahnya menjadi int sambil mengulang?
Guido
2

Hanya tipe primitif yang didukung untuk serialisasi oleh config parser. Saya akan menggunakan JSON atau YAML untuk persyaratan semacam itu.

M. Utku ALTINKAYA
sumber
terima kasih atas klarifikasi, utku. satu-satunya masalah adalah bahwa saya tidak dapat menggunakan paket eksternal saat ini. Saya pikir saya akan menulis kelas sederhana untuk menangani ini. Saya akan membagikannya pada akhirnya.
pistacchio
Versi Python apa yang Anda jalankan? Modul JSON disertakan dengan 2.6.
Patrick Harrington
2

Saya menghadapi masalah yang sama di masa lalu. Jika Anda membutuhkan daftar yang lebih rumit, pertimbangkan untuk membuat parser Anda sendiri dengan mewarisi dari ConfigParser. Maka Anda akan menimpa metode get dengan itu:

    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

Dengan solusi ini Anda juga dapat mendefinisikan kamus di file konfigurasi Anda.

Tetapi berhati-hatilah! Ini tidak aman: ini berarti siapa pun dapat menjalankan kode melalui file konfigurasi Anda. Jika keamanan tidak menjadi masalah dalam proyek Anda, saya akan mempertimbangkan untuk menggunakan langsung kelas python sebagai file konfigurasi. Berikut ini jauh lebih kuat dan dapat dihabiskan daripada file ConfigParser:

class Section
    bar = foo
class Section2
    bar2 = baz
class Section3
    barList=[ item1, item2 ]
Mapad
sumber
Saya berpikir untuk melakukan ini, namun: mengapa tidak memiliki nilai-nilai konfigurasi yang diatur seperti barList=item1,item2dan kemudian memanggil if value.find(',') > 0: return value.split(','), atau lebih baik lagi, memiliki aplikasi mengurai semua opsi konfigurasi sebagai daftar, dan hanya .split(',')semuanya secara membabi buta?
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(',')

Jadi sekarang config.cfgfile saya , yang bisa terlihat seperti ini:

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

Dapat diuraikan menjadi benda yang cukup halus untuk proyek kecil saya.

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

Ini untuk penguraian konfigurasi sederhana yang sangat cepat, Anda kehilangan semua kemampuan untuk mengambil int, bools, dan tipe output lainnya tanpa mengubah objek yang dikembalikan Parser, atau melakukan kembali pekerjaan parsing yang dilakukan oleh kelas Parser di tempat lain.

Droogan
sumber
1

Saya menyelesaikan tugas serupa di proyek saya dengan bagian dengan kunci tanpa nilai:

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)

Keluaran:

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

app.config:

[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
penerima bayaran
sumber
0

json.loads& ast.literal_evaltampaknya berfungsi tetapi daftar sederhana dalam konfigurasi memperlakukan setiap karakter sebagai byte sehingga mengembalikan braket bahkan persegi ...

artinya jika config telah fieldvalue = [1,2,3,4,5]

kemudian config.read(*.cfg) config['fieldValue'][0]kembali [di tempat1

Abhishek Jain
sumber
0

Seperti yang disebutkan oleh Peter Smit ( https://stackoverflow.com/a/11866695/7424596 ) Anda mungkin ingin memperluas ConfigParser, di samping itu, Interpolator dapat digunakan untuk secara otomatis mengkonversi ke dan dari daftar.

Untuk referensi di bagian bawah Anda dapat menemukan kode yang secara otomatis mengubah konfigurasi seperti:

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

Jadi, jika Anda meminta kunci, Anda akan mendapatkan:

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

Kode:

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 diingat pentingnya indentdation. Seperti yang dibaca dalam string doc ConfigParser:

Nilai dapat menjangkau beberapa baris, asalkan mereka indentasi lebih dalam dari baris pertama dari nilai. Bergantung pada mode parser, baris kosong dapat diperlakukan sebagai bagian dari nilai multiline atau diabaikan.

Dominik Maszczyk
sumber