Sử dụng ConfigParser để đọc tệp không có tên phần


87

Tôi đang sử dụng ConfigParserđể đọc cấu hình thời gian chạy của một tập lệnh.

Tôi muốn có sự linh hoạt trong việc không cung cấp tên phần (có những tập lệnh đủ đơn giản; chúng không cần 'phần'). ConfigParsersẽ ném ra một NoSectionErrorngoại lệ và sẽ không chấp nhận tệp.

Làm cách nào tôi có thể làm cho ConfigParser chỉ cần truy xuất các (key, value)bộ giá trị của tệp cấu hình mà không có tên phần?

Ví dụ:

key1=val1
key2:val2

Tôi không muốn ghi vào tệp cấu hình.


Câu trả lời:


52

Alex Martelli đã cung cấp một giải pháp để sử dụng ConfigParserphân tích cú pháp .propertiescác tệp (rõ ràng là các tệp cấu hình không có phần).

Giải pháp của ông là một trình bao bọc giống như tệp sẽ tự động chèn một tiêu đề phần giả để đáp ứng ConfigParsercác yêu cầu của.


+1 vì đó chính xác là những gì tôi sắp đề xuất. Tại sao phải thêm tất cả sự phức tạp khi tất cả những gì bạn phải làm chỉ là thêm một phần!
jathanism

5
@jathanism: có những trường hợp mà bạn muốn làm việc với các tập tin cấu hình / tài sản hiện có, được đọc bởi mã Java hiện và bạn không biết nguy cơ thay đổi các tiêu đề
tshepang

42

Khai sáng bởi câu trả lời này của jterrace , tôi đưa ra giải pháp này:

  1. Đọc toàn bộ tệp thành một chuỗi
  2. Tiền tố có tên phần mặc định
  3. Sử dụng StringIO để bắt chước một đối tượng giống tệp
ini_str = '[root]\n' + open(ini_path, 'r').read()
ini_fp = StringIO.StringIO(ini_str)
config = ConfigParser.RawConfigParser()
config.readfp(ini_fp)


CHỈNH SỬA cho người dùng google trong tương lai: Kể từ Python 3.4+ readfpkhông được dùng nữa và StringIOkhông còn cần thiết nữa. Thay vào đó, chúng ta có thể sử dụng read_stringtrực tiếp:

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

config_parser = ConfigParser.RawConfigParser()
config_parser.read_string(file_content)

Điều này cũng làm việc kỳ diệu để phân tích cú pháp một Makefile đơn giản (chỉ với bí danh)! Đây là một tập lệnh đầy đủ để thay thế bí danh bằng các lệnh đầy đủ của chúng bằng Python , lấy cảm hứng từ câu trả lời này.
gaborous

41

Bạn có thể làm điều này trong một dòng mã.

Trong python 3, hãy thêm tiêu đề phần giả mạo vào dữ liệu tệp cấu hình của bạn và chuyển nó vào 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.

Bạn cũng có thể sử dụng itertools.chain()để mô phỏng tiêu đề phần read_file(). Cách này có thể tiết kiệm bộ nhớ hơn so với cách tiếp cận ở trên, có thể hữu ích nếu bạn có tệp cấu hình lớn trong môi trường thời gian chạy bị hạn chế.

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)

Trong python 2, hãy thêm tiêu đề phần giả mạo vào dữ liệu tệp cấu hình của bạn, bọc kết quả trong một StringIOđối tượng và chuyển nó vào 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)

Với bất kỳ cách tiếp cận nào trong số này, cài đặt cấu hình của bạn sẽ có sẵn trong parser.items('top') .

Bạn cũng có thể sử dụng StringIO trong python 3, có lẽ để tương thích với cả trình thông dịch python cũ và mới, nhưng lưu ý rằng nó hiện đang tồn tại trong iogói và readfp()hiện không được dùng nữa.

Ngoài ra, bạn có thể cân nhắc sử dụng trình phân tích cú pháp TOML thay vì ConfigParser.


18

Bạn có thể sử dụng thư viện ConfigObj để làm việc đó một cách đơn giản: http://www.voidspace.org.uk/python/configobj.html

Cập nhật: Tìm mã mới nhất ở đây .

Nếu bạn đang sử dụng Debian / Ubuntu, bạn có thể cài đặt mô-đun này bằng trình quản lý gói của mình:

apt-get install python-configobj

Một ví dụ về việc sử dụng:

from configobj import ConfigObj

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

8

Theo tôi, cách dễ nhất để làm điều này là sử dụng trình phân tích cú pháp CSV của python. Đây là một chức năng đọc / ghi thể hiện cách tiếp cận này cũng như một trình điều khiển thử nghiệm. Điều này sẽ hoạt động với điều kiện các giá trị không được phép có nhiều dòng. :)

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

+1 Sử dụng csvmô-đun thông minh để giải quyết ConfigParserkhiếu nại thông thường . Dễ dàng khái quát hơn và tương thích với cả Python 2 & 3 .
martineau

6

Bản thân gặp phải vấn đề này, tôi đã viết một trình bao bọc hoàn chỉnh cho ConfigParser (phiên bản trong Python 2) có thể đọc và ghi tệp không có phần một cách minh bạch, dựa trên cách tiếp cận của Alex Martelli được liên kết trên câu trả lời được chấp nhận. Nó sẽ là một sự thay thế thả xuống cho bất kỳ cách sử dụng nào của ConfigParser. Đăng nó trong trường hợp bất cứ ai có nhu cầu tìm thấy trang này.

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)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.