configparser 从 zip 加载配置文件

Posted

技术标签:

【中文标题】configparser 从 zip 加载配置文件【英文标题】:configparser loading config files from zip 【发布时间】:2015-07-13 19:43:24 【问题描述】:

我正在创建一个从压缩文件加载和运行 python 脚本的程序。除了这些 python 脚本,我还有一个配置文件,我以前使用 configparser 从程序的未压缩版本中加载信息。

是否可以直接使用 configparser 直接读取 zip 文件中的配置文件?还是我必须将其解压缩到临时文件夹并从那里加载?

我试过直接给出路径:

>>> sysconf = configparser.ConfigParser()
>>> sysconf.read_file("compressed.zip/config_data.conf")

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/configparser.py", line 691, in read_file
    self._read(f, source)
  File "/usr/local/lib/python3.4/configparser.py", line 1058, in _read
    raise MissingSectionHeaderError(fpname, lineno, line)
configparser.MissingSectionHeaderError: File contains no section headers.
file: '<???>', line: 1

没用。没有惊喜。

然后我尝试使用 zipfile

 >>> zf = zipfile.ZipFile("compressed.zip")
 >>> data = zf.read("config_data.conf")
 >>> sysconf = configparser.ConfigParser()
 >>> sysconf.read_file(data)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/configparser.py", line 691, in read_file
    self._read(f, source)
  File "/usr/local/lib/python3.4/configparser.py", line 1009, in _read
    if line.strip().startswith(prefix):
AttributeError: 'int' object has no attribute 'strip'

发现也没用。

所以我求助于创建一个临时文件夹,解压缩到它,然后在那里读取 conf 文件。如果可能的话,我真的很想避免这种情况,因为 conf 文件是唯一的限制因素。我现在可以(并且正在)从 zip 文件中加载 python 模块就好了。

如果有办法将文件的原始文本直接传递给 configparser,我可以获得文件的原始文本,但搜索文档时我空手而归。

更新: 我尝试使用 stringIO 作为文件对象,它似乎有点工作。 configparser 不会拒绝它,但它也不喜欢它。

>>> zf = zipfile.ZipFile("compressed.zip")
>>> data = zf.read(config_data.conf)
>>> confdata = io.StringIO(str(data))
>>> sysconf = configparser.ConfigParser()
>>> sysconf.readfp(confdata)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/configparser.py", line 736, in readfp
    self.read_file(fp, source=filename)
  File "/usr/local/lib/python3.4/configparser.py", line 691, in read_file
    self._read(f, source)
  File "/usr/local/lib/python3.4/configparser.py", line 1058, in _read
    raise MissingSectionHeaderError(fpname, lineno, line)
configparser.MissingSectionHeaderError: File contains no section headers.
file: '<???>', line: 1
(continues to spit out the entire contents of the file)

如果我改用 read_file,它不会出错,但也不会加载任何内容。

>>> zf = zipfile.ZipFile("compressed.zip")
>>> data = zf.read(config_data.conf)
>>> confdata = io.StringIO(str(data))
>>> sysconf = configparser.ConfigParser()
>>> sysconf.read_file(confdata)
>>> sysconf.items("General") #(this is the main section in the file)
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/configparser.py", line 824, in items
    d.update(self._sections[section])
KeyError: 'General'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/configparser.py", line 827, in items
    raise NoSectionError(section)
configparser.NoSectionError: No section: 'General'

【问题讨论】:

对于您最近的编辑,请将 str(data) 替换为 data.decode() 【参考方案1】:

如果有办法直接将文件的原始文本传递给 configparser,则可以获取该文件的原始文本

试试configparser.ConfigParser.read_string

当与适当的 ZIP 文件结合使用时,此代码适用于我:

import zipfile
import configparser

zf = zipfile.ZipFile("compressed.zip")
zf_config = zf.open("config_data.conf", "rU")
zf_config_data = zf_config.read().decode('ascii')

config = configparser.ConfigParser()
config.read_string(zf_config_data)
assert config['today']['lunch']=='cheeseburger'

经过思考,以下可能更合适:

import zipfile
import configparser
import io

zf = zipfile.ZipFile("compressed.zip")
zf_config = zf.open("config_data.conf", "rU")
zf_config = io.TextIOWrapper(zf_config)

config = configparser.ConfigParser()
config.read_file(zf_config)
assert config['today']['lunch']=='cheeseburger'

【讨论】:

【参考方案2】:

如 cmets 中所写,@matthewatabet 答案不适用于 Python 3.4(和更新的版本)。这是因为ZipFile.open 现在返回一个“类似字节”的对象,而不是一个“类似文件”的对象了。您可以使用:

codecs.getreader("utf-8")(config_file)

使用 UTF-8 编码将config_file 类字节对象转换为类文件对象。现在的代码是:

import zipfile, configparser, codecs

# Python >= 3.4
with zipfile.ZipFile("compressed.zip") as zf:
    config_file = zf.open("config_data.conf") # binary mode
    sysconfig = configparser.ConfigParser()
    sysconfig.read_file(codecs.getreader("utf-8")(config_file))

这似乎比创建string 更令人满意,但我不知道它是否更有效率......

编辑 从 Python 3.9 开始,zipfile 模块提供了一个可以处理文本和二进制模式的zipfile.Path.open method。默认为文本模式。以下代码工作正常:

# Python >= 3.9
with zipfile.ZipFile("compressed.zip") as zf:
    zip_path = zipfile.Path(zf)
    config_path = zip_path / "config_data.conf"
    config_file = config_path.open() # text mode
    sysconfig = configparser.ConfigParser()
    sysconfig.read_file(config_file)

【讨论】:

适用于 Python 3.8,但不适用于 3.9 和 3.10。 TypeError: can't concat str to bytes. @AndrewSmeltzov 刚刚用 3.9 测试了一个最小的例子,它运行良好。你能给出完整的堆栈跟踪吗? 我的错,你的工作正常。原来我的例子有点不同。我使用 ZipFile.Path.open() 而不是 ZipFile.open()。无法真正发现问题,因为文档指出一个调用另一个:docs.python.org/3.10/library/zipfile.html#zipfile.Path.open 如果这很重要,我使用 docker image python:3.9-alpine。有趣的是它适用于 3.8。这是代码和跟踪godbolt.org/z/xaTvdvn56 @AndrewSmeltzov 感谢您的反馈。来自文档:ZipFile.open:“以二进制文件类对象的形式访问存档的成员。” zipfile.Path.open:“在 3.9 版中更改:添加了对打开的文本和二进制模式的支持。” codecs 技巧现在是不必要的,因为默认模式现在是文本。我会更新我的答案。 对,我怎么会错过。在这种情况下不需要codecs 操作。谢谢!【参考方案3】:

ZipFile 不仅支持read,还支持open,它返回一个类似文件的对象。所以,你可以这样做:

zf = zipfile.ZipFile("compressed.zip")
config_file = zf.open("config_data.conf")
sysconfig = configparser.ConfigParser()
sysconfig.readfp(config_file)

【讨论】:

你测试了吗?我无法让configparser 喜欢open 返回的类文件对象。 对其进行了更多测试,它似乎工作正常。你有错误吗? 在 Ubuntu 14.04 上运行您的代码的 Python 3.4.0 会产生错误。 TypeError: startswith first arg must be bytes or a tuple of bytes, not str 抱歉,没有运行 3.4。

以上是关于configparser 从 zip 加载配置文件的主要内容,如果未能解决你的问题,请参考以下文章

python—模块-configparser

python ConfigParser从配置文件中检索变量的示例

Python中配置文件解析模块-ConfigParser

无法在 Databricks 中使用 Configparser 读取配置文件

python读写修改配置文件(ini)

如何使用Python3读写INI配置文件