我正在编写我的第一个 python 包,我想将它上传到 PyPI。我根据这个 blog post 构建了我的代码.
我想将用户设置存储在 config.ini 文件中。在同一包中的单独 python 模块中读取一次(每次运行包时),并将用户设置保存在该模块的全局变量中。稍后将它们导入其他模块。
为了重现错误,我刚刚在博文中描述的模板中编辑了几行代码。 (请引用它,因为在这里重新创建整个问题需要太多的输入。)
唯一的区别是我的 stuff.py
像这样从配置文件中读取:
from ConfigParser import SafeConfigParser
config = SafeConfigParser()
config.read('config.ini')
TEST_KEY = config.get('main', 'test_key')
这是 config.ini
的内容(与 stuff.py
放在同一目录中):
[main]
test_key=value
而我的 bootstrap.py
只是导入并打印 TEST_KEY
from .stuff import TEST_KEY
def main():
print(TEST_KEY)
但是在执行包时,导入失败给出了这个错误
Traceback (most recent call last):
File "D:\Coding\bootstrap\bootstrap-runner.py", line 8, in <module>
from bootstrap.bootstrap import main
File "D:\Coding\bootstrap\bootstrap\bootstrap.py", line 11, in <module>
from .stuff import TEST_KEY
File "D:\Coding\bootstrap\bootstrap\stuff.py", line 14, in <module>
TEST_KEY = config.get('main', 'test_key')
File "C:\Python27\Lib\ConfigParser.py", line 607, in get
raise NoSectionError(section)
ConfigParser.NoSectionError: No section: 'main'
Import 不断给出 ConfigParser.NoSectionError,但是如果你只构建/运行 stuff.py(我使用 sublime3),模块不会给出错误并且打印 TEST_KEY
给出 value
作为输出。
此外,当我只在一个目录中使用 3 个文件(config、stuff、main)并将 main 作为脚本执行时,这种导入方法确实有效。但是我不得不像这样导入它
from stuff import TEST_KEY
我只是使用那篇文章中描述的显式相对导入,但对这些没有足够的理解。我猜错误是由于项目结构和导入引起的,因为作为独立脚本运行 stuff.py
不会引发 ConfigParser.NoSectionError
。
其他读取配置文件一次然后在其他模块中使用数据的方法也非常有用。
最佳答案
这个问题有两个方面。 首先 是ConfigParser
的奇怪行为。当ConfigParser
无法找到.ini
文件时;由于某些烦人的原因,它从不给出 IOError
或指示它无法读取文件的错误。
在我的例子中,当 section
明显存在时,它一直给出 ConfigParser.NoSectionError
。当我发现 ConfigParser.NoSectionError
错误时,它给出了一个 ImportError
!但它从不告诉您它根本无法读取文件。
其次是如何安全地读取包中包含的数据文件。我发现这样做的唯一方法是使用 __file__
参数。对于 Python27 和 Python3,这就是您安全阅读上述问题中的 config.ini
的方式:
import os
try:
# >3.2
from configparser import ConfigParser
except ImportError:
# python27
# Refer to the older SafeConfigParser as ConfigParser
from ConfigParser import SafeConfigParser as ConfigParser
config = ConfigParser()
# get the path to config.ini
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')
# check if the path is to a valid file
if not os.path.isfile(config_path):
raise BadConfigError # not a standard python exception
config.read(config_path)
TEST_KEY = config.get('main', 'test_key') # value
这依赖于 config.ini
位于我们的包 bootstrap
内,并且预计会随它一起提供。
重要的一点是如何获得 config_path
:
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')
__file__
指的是当前正在执行的script
的位置。在我的问题中,这意味着 stuff.py
的位置,它位于 bootstrap
文件夹内,config.ini
也是如此。
上面这行代码的意思是;获取 stuff.py
的绝对路径;从该获取路径到包含它的目录;并将其与 config.ini
连接(因为它在同一目录中)以提供 config.ini
的绝对路径。然后您可以继续阅读它并引发 exception
以防万一。
即使您在 pip
上发布您的包并且用户从那里安装它,这也会起作用。
作为一个奖励,稍微偏离问题一点,如果您在pip
上发布您的包并在包中包含数据文件,您必须告诉 setuptools
在构建 sdist
和 bdist
时将它们包含在您的包中。因此,要在上面的包中包含 config.ini
,请将以下行添加到 setup.py
中的 setup
类调用中:
include_package_data = True,
package_data = {
# If any package contains *.ini files, include them
'': ['*.ini'],
},
但在某些情况下它仍然可能不起作用,例如。构建轮子等。所以你也可以在你的 MANIFEST.IN
文件中做同样的事情:
include LICENSE
include bootstrap/*.ini
关于python - 在 python 包中添加和读取 config.ini 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35236223/