Python开发中配置文件的必要性及使用方法详解
(点击上方快速关注并设为明星,一起学习Python)

作者:100gle,一名实习不到两年的非正式文科生。他喜欢编码、写文章和修补各种新事物。目前从事大数据分析与挖掘相关工作。
为什么要写配置文件
在开发过程中,我们经常会使用一些固定的参数或常量。对于这些比较固定、常用的部分,往往会被写入固定的文件中,以避免不同模块代码中的重复,保持核心代码的整洁。
我们可以直接将这个固定文件写入.py文件,例如settings.py或config.py。这样做的好处是我们可以在同一个项目中直接通过import的方式导入其中的部分内容;但如果我们需要在其他非Python平台上配置文件共享时,编写单个.py并不是一个好的选择。这时候我们就应该选择一种通用的配置文件类型来存储这些固定的部分。目前常用且流行的配置文件格式类型主要有ini、json、toml、yaml、xml等,我们可以通过标准库或者第三方库来解析这些类型的配置文件。
.ini
ini 的意思是初始化。早期是Windows上配置文件的存储格式。 ini文件的写法很容易理解,而且往往比较简单。它通常由section、key和value组成,如下形式:
[localdb]
host = 127.0.0.1
user = root
password = 123456
port = 3306
database = mysql
Python自带的configparser标准库可以直接用来解析ini文件。例如,我们将上述内容保存在名为db.ini的文件中,然后使用read()方法解析读取,最后使用items()方法获取指定节点下的所有键值对。
>>> from configparser import ConfigParser
>>> cfg = ConfigParser()
>>> cfg.read("/Users/Bobot/db.ini")
['/Users/Bobot/db.ini']
>>> cfg.items("localdb")
[('host', '127.0.0.1'), ('user', 'root'), ('password', '123456'), ('port', '3306'), ('database', 'mysql')]
需要注意的是,configparser默认以字符串的形式呈现值,所以这就是为什么我们不在db.ini文件中添加引号而是直接写上面的字面量。
获取到键值对后,我其实是直接将其转换为字典,然后对参数进行解包,以保持代码简单:
#!pip install pymysql
import pymysql
from configparser import ConfigParser
cfg = ConfigParser()
cfg.read("/Users/Bobot/db.ini")
db_cfg = dict(cfg.items("localdb"))
con = pymysql.connect(**db_cfg)
json
json格式可以说是我们常用的文件格式,也是互联网上流行的数据交换格式。另外,json有时也是一种配置文件。
例如,npm(类似于Python的pip的JavaScript包管理工具)和微软出品的广泛使用的VSCode编辑器都使用json来编写配置参数。
与configparser一样,Python也内置了json标准库,可以通过load()和loads()方法导入文件和字符串json内容。
{
"localdb":{
"host": "127.0.0.1",
"user": "root",
"password": "123456",
"port": 3306,
"database": "mysql"
}
}
我们将上面的内容保存为db.json,然后读取解析。 json库读取json文件比较简单容易,很容易解析成Python字典对象。
>>> import json
>>> from pprint import pprint
>>>
>>> with open('/Users/Bobot/db.json') as j:
... cfg = json.load(j)['localdb']
...
>>> pprint(cfg)
{'database': 'mysql',
'host': '127.0.0.1',
'password': '123456',
'port': 3306,
'user': 'root'}
使用json文件配置的缺点是语法标准受到严格限制。其中一个诟病是无法在其中写注释,除非使用 json 类型的其他超集作为替代(VSCode 中可以写注释的 json 参数配置文件就是一种替代(一种解决方案);在同时存在嵌套太深的问题,容易导致错误,也不适合写长或复杂的参数配置信息。
托姆尔
toml 格式(或 tml 格式)是 Github 联合创始人 Tom Preston-Werner 提出的一种配置文件格式。据维基百科介绍,toml于2013年7月首次提出,距今已有7年;它在某些方面也与后面要讨论的 yaml 文件有些类似,但是如果你知道 yaml 规范有几十页时(是的,确实是几十页……),你可能不太愿意写这么复杂的配置文件,那么toml格式是一个不错的选择。
toml格式大致如下:
01-toml风格
从这里可以看出,toml与前面提到的ini文件有些类似。但它的扩展远不止ini。
在示例图中我们可以看到除了基本的字符串之外,还进一步支持了时间戳、布尔值、数组等,而且风格与Python原生的写法非常相似。
当然,这里我不会过多介绍toml格式的规范。官方规范文档已经有人翻译出来了,有兴趣的朋友可以直接查看。
开发者为适合Python方式的配置文件类型创建了相应的“轮子”。目前Github上star最多的版本是uiri/toml版本,但是这个版本只通过了v0.5版本的toml规范。但使用起来非常简单。我们可以通过pip命令来安装。
pip install toml
这个库的解析方法很简单,有点类似于json库的解析用法,即通过load()或loads()进行解析;同样,转换和导出的用法也类似。
例如,我们现在将以下内容写入config.toml:
[mysql]
host = "127.0.0.1"
user = "root"
port = 3306
database = "test"
[mysql.parameters]
pool_size = 5
charset = "utf8"
[mysql.fields]
pandas_cols = [ "id", "name", "age", "date"]
然后我们可以读取toml库中的load()方法:
>>> import toml
>>> import os
>>> from pprint import pprint
>>> cfg = toml.load(os.path.expanduser("~/Desktop/config.toml"))
>>> pprint(cfg)
{'mysql': {'database': 'test',
'fields': {'pandas_cols': ['id', 'name', 'age', 'date']},
'host': '127.0.0.1',
'parameters': {'charset': 'utf8', 'pool_size': 5},
'port': 3306,
'user': 'root'}}
可以看到toml文件间接转换成了字典类型。当然,json版本就是这样写的(只需要把单引号换成双引号即可),方便我们后续的调用或者参数传递。
yaml
yaml 格式(或 yml 格式)是目前流行的配置文件。它是早在 2001 年由一位名叫 Clark Evans 的人提出的。它也是一种广泛使用的配置文件类型,通常在 Docker 容器中。 docker-compose.yml 配置文件对于经常使用 Docker 进行部署的人来说应该很熟悉。
yaml文件的设计灵感来自于Python、XML等,所以在使用的时候可以清晰的看到这些部分的影子。
在上一篇关于toml内容的内容中,我提到过yaml的规范内容可以说是冗长而复杂,多达80页(厉害的真是太可怕了……)。
02-yaml规范页面
所以感兴趣的朋友可以自行学习相关用法。
YAML官方已经提供了相应的Python库进行支持,即PyYAML;当然,我们还需要提前安装:
pip install pyyaml
和json库、toml库一样,都是通过load()方法加载的。
需要注意的是,使用load()方法时存在一定的安全风险。从Cisco Talos的这份报告中我们可以看到,如果加载未知或不受信任的yaml文件,则可能存在被攻击的风险。以及网络安全风险,因为它可以直接调用相应的Python函数来执行攻击者所需的命令,比如在yaml文件中写入这一段:
# 使用Linux和macOS的朋友不要轻易尝试
!!python/object/apply:os.system ["rm -rf /"]
因此,最好使用 safe_load() 而不是 load() 方法。
这与Python内置字符串标准库中Template类的substitute()模板方法具有相同的安全风险,因此使用safe_substitute()代替也是同样的道理。
比如我们现在将之前的一些配置信息写入到config.yaml文件中:
mysql:
host: "127.0.0.1"
port: 3306
user: "root"
password: "123456"
database: "test"
parameter:
pool_size: 5
charset: "utf8"
fields:
pandas_cols:
- id
- name
- age
- date
然后我们通过safe_load()方法解析它:
>>> import os
>>> from pprint import pprint
>>>
>>> with open(os.path.expanduser("~/config.yaml"), "r") as config:
... cfg = yaml.safe_load(config)
...
>>> pprint(cfg)
{'mysql': {'database': 'test',
'fields': {'pandas_cols': ['id', 'name', 'age', 'date']},
'host': '127.0.0.1',
'parameter': {'charset': 'utf8', 'pool_size': 5},
'password': '123456',
'port': 3306,
'user': 'root'}}
可以看到,最终结果与之前toml库的解析结果基本一致。
结束
本文列出了一些主流常见的配置文件类型及其Python读取方法。有些读者可能会发现没有XML格式的内容。跟Java语言打交道的朋友可能会遇到比较多的xml配置文件,但是xml文件的可读性确实让人望而生畏;不懂xml文件的朋友可以使用Chrome浏览器进入一个网站,按F12进入开发者后,你会看到密密麻麻的html元素就是.xml的缩影。
除了这些主流的配置文件类型之外,还可以使用一些.cfg、.properties等作为配置文件。即使如开头提到的,您可以使用单独的 .py 文件写入各种配置信息并将其作为配置文件导入。一切都很好,但是跨语言共享时可能会遇到一些障碍。因此,本文不会过多介绍,有兴趣的朋友可以自行了解。
本文列出的配置文件类型从上到下复杂度依次递增:ini < json ≈ toml < yaml。它们各有优缺点,您可以根据自己的实际需求和团队协作需求进行选择。
(完)
看完本文有收获?请转发分享给更多人 关注「Python那些事」,做全栈开发工程师 点「在看」的人都变好看了哦