Python 3 拿捏configparser和pyyaml配置文件解析库

文章开篇

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库的问题;
最终的选择取决于你的具体需求、项目规模、以及你对于解析库的熟悉程度
如果你已经对其中一种库非常熟悉,并且使用它能够满足你的需求,那么最好继续使用它;

  • 33
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值