Menggunakan ConfigParser untuk membaca file tanpa nama bagian

90

Saya menggunakan ConfigParseruntuk membaca konfigurasi runtime dari sebuah skrip.

Saya ingin memiliki fleksibilitas untuk tidak memberikan nama bagian (ada skrip yang cukup sederhana; mereka tidak memerlukan 'bagian'). ConfigParserakan mengeluarkan NoSectionErrorpengecualian, dan tidak akan menerima file tersebut.

Bagaimana cara membuat ConfigParser mengambil (key, value)tupel dari file konfigurasi tanpa nama bagian?

Misalnya:

key1=val1
key2:val2

Saya lebih suka tidak menulis ke file konfigurasi.

Escualo
sumber
Kemungkinan duplikat file parsing .properties dengan Python
Håken Lid

Jawaban:

53

Alex Martelli memberikan solusi untuk digunakan ConfigParseruntuk mengurai .propertiesfile (yang tampaknya merupakan file konfigurasi tanpa bagian).

Solusinya adalah pembungkus seperti file yang secara otomatis akan memasukkan judul bagian tiruan untuk memenuhi ConfigParserpersyaratan.

Will McCutchen
sumber
+1 karena itulah yang akan saya sarankan. Mengapa menambahkan semua kerumitan ketika yang harus Anda lakukan hanyalah menambahkan satu bagian!
jatanisme
5
@jatanisme: ada kasus di mana Anda ingin bekerja dengan file config / properti yang ada, yang dibaca oleh kode Java yang ada dan Anda tidak tahu risiko memodifikasi tajuk tersebut
tshepang
44

Tercerahkan oleh jawaban ini oleh jterrace , saya menemukan solusi ini:

  1. Baca seluruh file menjadi string
  2. Awalan dengan nama bagian default
  3. Gunakan StringIO untuk meniru objek seperti file
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


EDIT untuk calon karyawan Google: Mulai Python 3.4+ readfpsudah tidak digunakan lagi, dan StringIOtidak diperlukan lagi. Sebagai gantinya kita bisa read_stringlangsung menggunakan :

with open('config_file') as f:
    file_content = '[dummy_section]\n' + f.read()

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)
xmo
sumber
Ini juga bekerja sangat baik untuk mengurai Makefile sederhana (hanya dengan alias)! Berikut ini skrip lengkap untuk menggantikan alias dengan perintah lengkapnya di Python , terinspirasi oleh jawaban ini.
gaborous
42

Anda dapat melakukan ini dalam satu baris kode.

Di python 3, tambahkan header bagian palsu ke data file konfigurasi Anda, dan teruskan ke read_string().

from configparser import ConfigParser

parser = ConfigParser()
with open("foo.conf") as stream:
    parser.read_string("[top]\n" + stream.read())  # This line does the trick.

Anda juga dapat menggunakan itertools.chain()untuk mensimulasikan tajuk bagian untuk read_file(). Ini mungkin lebih hemat memori daripada pendekatan di atas, yang mungkin berguna jika Anda memiliki file konfigurasi besar dalam lingkungan runtime yang dibatasi.

from configparser import ConfigParser
from itertools import chain

parser = ConfigParser()
with open("foo.conf") as lines:
    lines = chain(("[top]",), lines)  # This line does the trick.
    parser.read_file(lines)

Di python 2, tambahkan header bagian palsu ke data file konfigurasi Anda, bungkus hasilnya dalam sebuah StringIOobjek, dan teruskan ke readfp().

from ConfigParser import ConfigParser
from StringIO import StringIO

parser = ConfigParser()
with open("foo.conf") as stream:
    stream = StringIO("[top]\n" + stream.read())  # This line does the trick.
    parser.readfp(stream)

Dengan salah satu pendekatan ini, setelan konfigurasi Anda akan tersedia di parser.items('top') .

Anda dapat menggunakan StringIO di python 3 juga, mungkin untuk kompatibilitas dengan interpreter python lama dan baru, tetapi perhatikan bahwa sekarang ada di dalam iopaket dan readfp()sekarang sudah usang.

Atau, Anda dapat mempertimbangkan untuk menggunakan parser TOML daripada ConfigParser.

ʇsәɹoɈ
sumber
18

Anda dapat menggunakan pustaka ConfigObj untuk melakukannya dengan mudah: http://www.voidspace.org.uk/python/configobj.html

Diperbarui: Temukan kode terbaru di sini .

Jika Anda menggunakan Debian / Ubuntu, Anda dapat menginstal modul ini menggunakan manajer paket Anda:

apt-get install python-configobj

Contoh penggunaan:

from configobj import ConfigObj

config = ConfigObj('myConfigFile.ini')
config.get('key1') # You will get val1
config.get('key2') # You will get val2
Blueicefield
sumber
8

Cara termudah untuk melakukannya adalah dengan menggunakan parser CSV python, menurut saya. Berikut adalah fungsi baca / tulis yang mendemonstrasikan pendekatan ini serta test driver. Ini harus berfungsi asalkan nilainya tidak boleh multi-baris. :)

import csv
import operator

def read_properties(filename):
    """ Reads a given properties file with each line of the format key=value.  Returns a dictionary containing the pairs.

    Keyword arguments:
        filename -- the name of the file to be read
    """
    result={ }
    with open(filename, "rb") as csvfile:
        reader = csv.reader(csvfile, delimiter='=', escapechar='\\', quoting=csv.QUOTE_NONE)
        for row in reader:
            if len(row) != 2:
                raise csv.Error("Too many fields on row with contents: "+str(row))
            result[row[0]] = row[1] 
    return result

def write_properties(filename,dictionary):
    """ Writes the provided dictionary in key-sorted order to a properties file with each line of the format key=value

    Keyword arguments:
        filename -- the name of the file to be written
        dictionary -- a dictionary containing the key/value pairs.
    """
    with open(filename, "wb") as csvfile:
        writer = csv.writer(csvfile, delimiter='=', escapechar='\\', quoting=csv.QUOTE_NONE)
        for key, value in sorted(dictionary.items(), key=operator.itemgetter(0)):
                writer.writerow([ key, value])

def main():
    data={
        "Hello": "5+5=10",
        "World": "Snausage",
        "Awesome": "Possum"
    }

    filename="test.properties"
    write_properties(filename,data)
    newdata=read_properties(filename)

    print "Read in: "
    print newdata
    print

    contents=""
    with open(filename, 'rb') as propfile:
        contents=propfile.read()
    print "File contents:"
    print contents

    print ["Failure!", "Success!"][data == newdata]
    return

if __name__ == '__main__': 
     main() 
nacitar sevaht
sumber
+1 Penggunaan csvmodul yang cerdas untuk menyelesaikan ConfigParserkeluhan umum . Lebih mudah digeneralisasikan dan dibuat agar kompatibel dengan Python 2 & 3 .
martineau
6

Setelah mengalami masalah ini sendiri, saya menulis pembungkus lengkap untuk ConfigParser (versi Python 2) yang dapat membaca dan menulis file tanpa bagian secara transparan, berdasarkan pendekatan Alex Martelli yang ditautkan pada jawaban yang diterima. Ini harus menjadi pengganti drop-in untuk penggunaan ConfigParser apa pun. Mempostingnya jika ada yang membutuhkannya menemukan halaman ini.

import ConfigParser
import StringIO

class SectionlessConfigParser(ConfigParser.RawConfigParser):
    """
    Extends ConfigParser to allow files without sections.

    This is done by wrapping read files and prepending them with a placeholder
    section, which defaults to '__config__'
    """

    def __init__(self, *args, **kwargs):
        default_section = kwargs.pop('default_section', None)
        ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)

        self._default_section = None
        self.set_default_section(default_section or '__config__')

    def get_default_section(self):
        return self._default_section

    def set_default_section(self, section):
        self.add_section(section)

        # move all values from the previous default section to the new one
        try:
            default_section_items = self.items(self._default_section)
            self.remove_section(self._default_section)
        except ConfigParser.NoSectionError:
            pass
        else:
            for (key, value) in default_section_items:
                self.set(section, key, value)

        self._default_section = section

    def read(self, filenames):
        if isinstance(filenames, basestring):
            filenames = [filenames]

        read_ok = []
        for filename in filenames:
            try:
                with open(filename) as fp:
                    self.readfp(fp)
            except IOError:
                continue
            else:
                read_ok.append(filename)

        return read_ok

    def readfp(self, fp, *args, **kwargs):
        stream = StringIO()

        try:
            stream.name = fp.name
        except AttributeError:
            pass

        stream.write('[' + self._default_section + ']\n')
        stream.write(fp.read())
        stream.seek(0, 0)

        return ConfigParser.RawConfigParser.readfp(self, stream, *args,
                                                   **kwargs)

    def write(self, fp):
        # Write the items from the default section manually and then remove them
        # from the data. They'll be re-added later.
        try:
            default_section_items = self.items(self._default_section)
            self.remove_section(self._default_section)

            for (key, value) in default_section_items:
                fp.write("{0} = {1}\n".format(key, value))

            fp.write("\n")
        except ConfigParser.NoSectionError:
            pass

        ConfigParser.RawConfigParser.write(self, fp)

        self.add_section(self._default_section)
        for (key, value) in default_section_items:
            self.set(self._default_section, key, value)
danielkza
sumber
5

Jawaban Blueicefield menyebutkan configobj, tetapi lib aslinya hanya mendukung Python 2. Sekarang ia memiliki port yang kompatibel dengan Python 3+:

https://github.com/DiffSK/configobj

API belum berubah, lihat dok .

laike9m
sumber
Meskipun tautan ini mungkin menjawab pertanyaan, lebih baik menyertakan bagian penting dari jawaban di sini dan menyediakan tautan untuk referensi. Jawaban link saja bisa menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan
ks
Dan bagaimana jika API berubah? Anda selalu dapat mengedit jawaban jika sudah lama, baik itu tautan atau cuplikan kode. Saya telah melakukannya berkali-kali, saya tidak mengerti mengapa Anda menganggapnya sebagai masalah.
laike9m