Python 的 ConfigParser 中的意外设计选择

Unexpected design choice in Python's ConfigParser

我尝试像这样读取和写入配置文件:

with open(file, 'r') as ini:
        config = ConfigParser()
        config.read(ini)
        config['section'] = {'foo':45}
with open(file, 'w') as ini:
        config.write(ini)

我只是无法读取已保存的数据。我花了很长时间才发现我实际上必须像这样从文件中读取:

config.read(file)

为什么不一致?为什么我必须以两种不同的方式阅读和写作?这种设计选择是否有充分的理由,关于 I/O 我还不知道的事情?

为什么当我尝试从文件缓冲区读取时它没有引发异常?

是否有其他内置函数在文件处理方面不一致?

从文档中实际上有两种(如果算上 list 参数,则为三种)方法:

import ConfigParser, os

config = ConfigParser.ConfigParser()
config.readfp(open('defaults.cfg'))
config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')])

filename 版本还允许您传入多个文件名,ConfigParser 将自动处理(忽略)丢失的文件。这是配置文件的常见习惯用法,您可能有一个默认配置文件,然后是一个本地定义的配置文件。

为什么它被称为 readfp 而不是 write 而不是 writefp... 是不一致的?您可以阅读其 history. Also here.

最后,设计(和设计师)并不总是完美的,特质并不总是被抓住,但一旦它成为标准库,接口就会被冻结。

我们可以查看 ConfigParser 源代码以了解为什么带有 fileobjectread() 被静默忽略:

def read(self, filenames):
    """Read and parse a filename or a list of filenames.

    Files that cannot be opened are silently ignored; this is
    designed so that you can specify a list of potential
    configuration file locations (e.g. current directory, user's
    home directory, systemwide directory), and all existing
    configuration files in the list will be read.  A single
    filename may also be given.

    Return list of successfully read files.
    """
    if isinstance(filenames, basestring):
        filenames = [filenames]
    read_ok = []
    for filename in filenames:
        try:
            fp = open(filename)
        except IOError:
            continue
        self._read(fp, filename)
        fp.close()
        read_ok.append(filename)
    return read_ok

哦——哇——这会很有趣!

您的 fileobject 不是 basestring,因此它假定它必须是可迭代的。然后它在 fileobject 上迭代。这意味着它会读取您的文件以获取它尝试打开的文件名列表。

例如,我做了一个文件f并用a-g填充它,每行一个字母。 strace 显示:

open("f", O_RDONLY)                     = 3
open("a\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("b\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("c\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("d\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("e\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("f\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)
open("g\n", O_RDONLY)                   = -1 ENOENT (No such file or directory)

...并且由于 API 旨在忽略无法打开的文件,它只是忽略错误。

来自文档,

If none of the named files exist, the ConfigParser instance will contain an empty dataset. An application which requires initial values to be loaded from a file should load the required file or files using readfp() before calling read() for any optional files:

这种行为令人惊讶,我已经提交 http://bugs.python.org/issue27351 以确保他们了解这种极端情况。