文章开篇
Python的魅力,犹如星河璀璨,无尽无边;人生苦短、我用Python!
配置文件的作用?
Python中的配置文件是包含项目设置和参数的数据文件;
它的主要作用是存储和管理程序启动时的配置数据,并在程序下次启动时自动读取这些配置,避免每次都需要手动输入或修改;
这些文件有多种格式,如YAML、JSON、INI等,并且Python提供了多种方法来读取和解析它们;
配置文件的好处?
使用配置文件可以极大提高代码的便捷性和可维护性,允许用户轻松地调整程序参数和设置,无需修改代码,从而简化了配置管理,使其更灵活、高效。此外,配置文件还促进了程序的模块化设计,使配置信息更易于管理和维护;
- 灵活性:配置文件允许用户在不修改源代码的情况下,改变程序的行为或设置;
- 可维护性:配置信息通常存储在单独的文件中,这有助于保持代码清晰和整洁;
- 可扩展性:使用配置文件可以很容易地添加新的配置项,而不需要修改源代码;
- 环境隔离:配置文件可以为不同的运行环境(如开发、测试、生产)设置不同的配置;
- **安全性:**配置文件可以用来存储敏感信息(如数据库密码、API密钥等),而不是选择硬编码;
在选择配置文件解析库时,configparser和pyyaml是两个常用的选项:
1.configparser
- 优点:
属于标准库,无需额外安装;
支持基本的INI文件格式;
易于使用,提供直观的API来读取和写入配置信息;
支持插值,允许在一个配置值中引用另一个配置值;- 缺点:
功能相对有限,不支持复杂的数据结构(如列表、字典等);
INI文件格式可能不够灵活,不适合表达复杂的配置需求;
2.PyYaml
- 优点:
YAML文件通常更易于人类编辑和理解;
支持YAML文件格式,这种格式易于阅读和编写,支持复杂的数据结构;
可以轻松处理嵌套的数据结构,如字典、列表等;
- 缺点:
属于第三方库,需要额外安装;
使用上可能稍复杂一些,尤其是对于初学者来说;
在某些情况下,YAML格式可能导致解析歧义;
3.抉择
- 如果配置需求相对简单,并且希望使用内置库,configparser是一个不错的选择;
- 如果配置需求比较复杂,或者希望使用一种更易于阅读和编写的格式,PyYaml可能更适合你;
配置文件的结构?
1.ini/conf/cnf等后缀文件
这类配置文件主要由项(section)、键(key)和值(value)三个元素构成;
示例如下
[mysql@imp]
host = 10.240.37.120
port = 1
username = impuser
password = Zy3306@Zkk120!
database = imp
2.yaml/yml后缀文件
YAML文件是一种高级标记语言,与普通配置文件相比,它能够表达更为复杂和灵活的数据结构;
其基本语法规则严谨而明确:
- **大小写敏感性:**YAML对大小写敏感,确保键和值的准确性;
- **缩进层级关系:**通过缩进来定义数据之间的层次和关系,结构清晰;
- **空格替代Tab:**在YAML中,不允许使用Tab键进行缩进,仅允许使用空格,确保一致性;
- **对齐的灵活性:**虽然缩进的空格数量在视觉上可能有所不同,但相同层级的元素必须保持左侧对齐,以维护结构的准确性;
- **注释功能:**YAML支持行注释,使用#开头,允许开发者在配置文件中添加说明和备注。
其支持的三种数据结构:- **标量:**表示单个值,是YAML中最基本的数据单位;
- **数组:**一组有序的值集合,又称为序列或列表,适用于表示有序数据;
- **对象:**一种键值对的集合,也称为映射、哈希或字典,用于组织相关数据;
Yaml的标量(单个值)形式,用冒号加空格连接值(: xx)
name: zhangsan
age: 18
address: shanghai
phone: 10086
Yaml的数组(序列/列表)形式,用短横线加空格(- )
fruits:
- apple
- banana
- cherry
- durian
Yaml的对象(映射/哈希/字典)形式,用冒号加空格连接键和值(: )
person:
name: John Doe
age: 30
is_student: false
address:
street: 123 Main St
city: Springfield
state: IL
Yaml的多种数据格式的混合示例
# 标量示例
version: 1.0
# 对象示例
database:
host: localhost
port: 3306
users:
- id: 1
name: admin
password: secret
- id: 2
name: user
password: userpass
# 数组示例
favorite_numbers: [1, 2, 3, 5, 8, 13, 21]
# 另一个对象示例,包含标量和数组
employee:
name: Alice
age: 28
departments:
- HR
- IT
skills:
programming: expert
communication: intermediate
is_full_time: true
Yaml文件更加复杂的格式(嵌套),稍后会拿这个例子进行编码
# 一个更复杂的结构,包含对象、数组和标量
company:
name: XYZ Corp
employees:
- name: Bob
position: CEO
- name: Alice
position: Software Engineer
departments:
- name: HR
managers:
- name: Carol
email: carol@xyzcorp.com
- name: Dave
email: dave@xyzcorp.com
- name: IT
managers:
- name: Eve
email: eve@xyzcorp.com
founding_date: 1990-01-01
解析结构1配置文件
1.ConfigParser模块
使用Python提供的标准库ConfigParser来解析ini/conf/cnf等后缀的配置文件。
from configparser import ConfigParser
config = ConfigParser() # 实例化
config.read('config.ini') # 读取配置文件
print(config.sections()) # 获取所有的section名称字符串,一列表返回
print(config.options('mysql@imp')) # 获取指定section下对应的配置项的所有的字符串名称,以列表返回
print(config.items('mysql@imp')) # 获取指定section下所有的配置项的键值对,二元元组
print(config.get('mysql@imp', 'port')) # 获取指定section下的指定key对应的value
print(config.getint('mysql@imp', 'port')) # 指定类型,帮我们转换类型
print(config["mysql@imp"]['host']) # 直接以字典取值的方式读取ini文件
2.ConfigParser模块完整封装
真实应用案例封装,可直接拿去运用到实际开发项目中;
ConfigparserUtils类是通过Python内置的configparser模块封装的工具类;
通过给定配置文件所在的路径,获取配置文件的操作对象,可进行以下功能:
- 获取配置文件操作对象
- 刷新config对象,重新加载配置文件,防止缓存
- 检查指定的section节点是否存在
- 获取所有的section节点
- 获取指定节点下所有选项(key)
- 获取指定section节点下指定option选项的value
- 获取指定section节点下所有option选项的value
- 修改指定section节点下option选项的value
- 将数据字典转换成config对象并写入文件
import configparser
import os
class ConfigparserUtils(object):
"""配置文件工具类封装"""
def __init__(self, path: str):
"""
对象初始化函数,在创建类对象时调用
:param path:
"""
self.path: str = path
self.config = self.__acquire_config_object(path)
def get_config_object(self):
return self.config
def __acquire_config_object(self, path: str):
"""
获取配置文件读取对象(私有方法)
:param path: 配置文件所在路径
:return: 配置文件处理对象
"""
__config = configparser.RawConfigParser()
if os.path.exists(path):
__config.read(filenames=path, encoding='utf-8')
return __config
else:
raise FileNotFoundError(f"加载[{path}]配置文件失败!")
def refresh_config_object(self):
# 刷新config对象,重新加载配置文件,防止缓存
self.config = self.__acquire_config_object(self.path)
def has_section(self, section):
"""
检查指定的section节点是否存在
:param section: 节点名称
:return: 如果节点存在则返回True,否则返回False
"""
return section in self.config.sections()
def acquire_all_section(self):
"""
获取所有的section节点
:return: 以列表形式返回配置文件中所有的节点名
"""
return self.config.sections()
def acquire_all_option(self, section):
"""
获取指定节点下所有选项(key)
:param section: 节点名称
:return: 以列表形式返回指定节点下所有的选项(key)
"""
return self.config.options(section)
def acquire_section_option(self, section: str, option: str):
"""
获取指定section节点下指定option选项的value
:param section: 节点名称
:param option: 节点下的选项(key)
:return:
"""
self.config.get(section, option)
if section in self.acquire_all_section():
if option in self.acquire_all_option(section):
return self.config.get(section, option)
else:
raise KeyError(f"上送选项[{option}]不存在!")
else:
raise KeyError(f"上送节点[{section}]不存在!")
def acquire_all_section_option(self, section: str):
"""
获取指定section节点下所有option选项的value
:param section: 节点
:return: 以列表形式返回指定节点下的所有选项(key)
"""
try:
return self.config.items(section)
except configparser.NoSectionError:
raise configparser.NoSectionError(f"上送节点[{section}]不存在!")
def modify_section_option(self, section: str, option: str, value: str):
"""
修改指定section节点下option选项的value
:param section: 节点名称
:param option: 选项名称
:param value: 新数据
:return:
"""
try:
self.config.set(section, option, value)
with open(file=self.path, mode='w', encoding='utf-8') as f:
self.config.write(f)
return True
except configparser.NoSectionError:
raise configparser.NoSectionError(f"上送节点[{section}]不存在!")
def dict_to_configure(self, dictionary: dict, section: str, mode: str = "a", new_path: str = None):
"""
将数据字典转换成config对象并写入文件
:param mode: 写入文件方式(追加或覆盖)
:param dictionary: 数据字典
:param section: 节点名称
:param new_path: 保存地址(如不指定则保存在实例化对象时给的文件)
:return:
"""
if self.has_section(section):
raise KeyError("节点已存在")
try:
__path = self.path if new_path is None else new_path
__config = configparser.RawConfigParser()
__config[section] = dictionary
with open(file=__path, mode=mode, encoding="UTF-8") as file_object:
__config.write(file_object)
return True
except Exception as e:
raise Exception("出现未知错误")
if __name__ == '__main__':
config_path = "./config.ini"
config = ConfigparserUtils(config_path)
print(config) # <__main__.ConfigparserUtils object at 0x7fea6005eb50>
print(type(config)) # <class '__main__.ConfigparserUtils'>
print("获取所有节点:", config.acquire_all_section())
print("获取指定节点下指定选项的值:", config.acquire_section_option("mysql@imp", "host"))
print("获取指定节点下所有选项:", config.acquire_all_option("mysql@imp"))
print("获取指定节点下所有选项和值(k-v):", config.acquire_all_section_option("mysql@imp"))
# 修改节点值
config.modify_section_option("mysql@imp", "port", "12345")
# 添加新节点
config.dict_to_configure({"demo1": "demo1"}, "demo", "a")
解析结构2配置文件
1.PyYaml模块
要解析YAML配置文件,需要先安装PyYaml库,使用pip install pyyaml命令;
# 安装 PyYAML 命令
pip install PyYAML
这是我已发布的推文中首次出现第三方模块,关于pip模块将会在其他篇章详细道来;
未来会讲解更多的第三方模块如pymysql、openpyxl、selenium、pytest、requests等;
并重开一个系列,开源接口、UI自动化框架和平台等进阶内容,请持续关注;
PyYaml库的使用极为便捷,它能够将完整的YAML配置文件内容轻松解析为Python字典形式返回,使得配置文件中的数据更易于在Python程序中处理和使用;
import yaml
with open('config.yaml', 'r', encoding='utf-8') as f:
config = yaml.load(f, Loader=yaml.FullLoader)#
print(config)
# 输出如下,请仔细观察与上述Yaml数据结构中的“一个更复杂的结构,包含对象、数组和标量”示例
# {
# "company": {
# "name": "XYZ Corp",
# "employees": [
# {
# "name": "Bob",
# "position": "CEO"
# },
# {
# "name": "Alice",
# "position": "Software Engineer"
# }
# ],
# "departments": [
# {
# "name": "HR",
# "managers": [
# {
# "name": "Carol",
# "email": "carol@xyzcorp.com"
# },
# {
# "name": "Dave",
# "email": "dave@xyzcorp.com"
# }
# ]
# },
# {
# "name": "IT",
# "managers": [
# {
# "name": "Eve",
# "email": "eve@xyzcorp.com"
# }
# ]
# }
# ],
# "founding_date": datetime.date(1990, 1, 1)
# }
# }
2.PyYaml模块完整封装
真实应用案例封装,可直接拿去运用到实际开发项目中;
YamlUtils类是通过Python的第三方模块PyYaml模块封装的工具类;
通过给定配置文件所在的路径,获取配置文件的存储数据,可进行以下功能:
- 加载yaml文件数据
- 获取指定路径下的数据
- 修改指定路径下的数据
- 保存yaml文件数据
class YamlUtils:
def __init__(self, file_path: str):
"""
YamlUtils类的初始化方法
:param file_path: yaml配置文件路径
"""
self.file_path = file_path
# 声明一个变量,用来存储配置文件中的数据
self.config_data = {}
def load_config(self) -> bool:
"""
加载yaml配置文件中的数据
:return:
"""
try:
with open(file=self.file_path, mode='r', encoding="utf-8") as file:
self.config_data = yaml.safe_load(file)
return True
except FileNotFoundError:
print(f"文件 {self.file_path} 不存在")
return False
except Exception as e:
print("未知异常:", e)
return False
def get_value(self, path: str):
"""
按照指定的路径获取配置文件中的数据
:param path: 路径,如果某一级是数字则认为配置文件中对应的层级是列表
:return:
"""
# 将路径分割成列表
keys = path.split('.')
current_dict = self.config_data
for k in keys:
# 字符k,以字典形式获取
if k in current_dict:
current_dict = current_dict[k]
# 数值k,以列表形式获取
elif k.isdigit() and (0 <= int(k) < len(current_dict)):
current_dict = current_dict[int(k)]
else:
return None
return current_dict
def set_value(self, path: str, value: Union[int, str, list, dict]):
"""
向配置文件中写入或修改某个路径的值
:param path: 路径
:param value: 值
:return:
"""
# 将路径分割成列表
keys = path.split('.')
current_dict = self.config_data
for i, k in enumerate(keys[:]):
if k not in current_dict:
current_dict[k] = {}
current_dict = current_dict[k]
current_dict[keys[-1]] = value
def save_config(self):
"""
将config_data字典中的数据保存到配置文件
:return:
"""
with open(file=self.file_path, mode='w', encoding="utf-8") as file:
yaml.safe_dump(self.config_data, file, default_flow_style=False)
# 使用示例
if __name__ == "__main__":
yaml_helper = YamlUtils('config.yaml')
if yaml_helper.load_config():
print("加载配置文件数据成功...")
print("获取指定路径下的数据:", yaml_helper.get_value('company.departments.0.managers'))
# 修改配置
a = [{'email': 'carol@xyzcorp.com', 'name': 'Carol'}, {'email': 'dave@xyzcorp.com', 'name': 'Dave'}]
yaml_helper.set_value('aaa.bbb.ccc.ddd', a)
yaml_helper.save_config()
print("配置文件保存成功...")
总结
关于选择configparser还是pyyaml库的问题;
最终的选择取决于你的具体需求、项目规模、以及你对于解析库的熟悉程度;
如果你已经对其中一种库非常熟悉,并且使用它能够满足你的需求,那么最好继续使用它;