Python小工具系列(1)----yaml操作

一. yaml基本概念

yaml简介

yaml有种含义:

  1. yaml是专门用来写配置文件的语言。

  2. yaml文件是一种常用的配置文件类型,后缀名是.yaml 或 .yml。

yaml的基本语法规则

  • 区分大小写
  • 使用缩进表示层级关系
  • 使用空格缩进,而非Tab键缩进
  • 缩进的空格数目不固定,只需要相同层级的元素左侧对齐
  • 文件中的字符串不需要引号标注,但若字符串中包含有特殊字符则需用引号标注
  • 注释标识为 #

yaml文件数据结构

  • 对象:键值对的集合(简称:"映射或字典")
    键值对用冒号:结构来表示,冒号与值之间需用空格分隔
  • 数组: 一组按序排列的值(简称"序列或表")
    数组前加有-符号,符号与值之间需用空格分隔
  • 纯量(scalars): 单个的、不可再分的值(如:字符串、bool值、整数、浮点数、时间、日期、null等) None值可用null~表示

二、需求分析

我写这个工具的本意是为了整合项目里的配置文件,之前是分散在几个ini文件中的,不好维护。 整合之后的话,我可能会需要对yaml进行比较复杂的操作,比如在一个yaml文件的中首部,尾部,任一位置,插入,删除,修改指定的yaml文档。

所以分析下来,我要实现的工具的功能是:

  1. 支持单个与多个yaml文档的场景
  2. 支持对同一个yaml文件的首部,尾部,任一位置,插入,删除,修改指定的yaml文档。

三、技术选型

操作yaml的常用python库有哪些,各自有什么优缺点

操作yaml的常用python库有yaml,PyYAML,ruamel.yaml,yamlize等。

yaml

其中首先排除yaml,它功能比较简陋,不符合我的需求。

yaml是 Python 标准库中的 YAML 解析器和生成器。它简单易用,并且对于大多数常见的 YAML 用例来说已经足够了。但是,它不支持 YAML 1.2 规范中的一些新特性,例如合并键和显式序列标签。

PyYAML

  • 优点:

    • 广泛使用: PyYAML是Python社区中最知名的YAML处理库之一,有着广泛的用户基础和成熟的应用案例。
    • 简单易用 : 提供了简洁的API,如yaml.safe_load()yaml.dump(),方便快速读写YAML数据。
    • 跨平台兼容: 支持多种操作系统和Python版本,兼容性良好。
    • 标准符合: 符合YAML 1.1规范,能够处理大部分常见YAML数据结构。
  • 缺点:

    • 部分功能缺失: 对较新的YAML 1.2规范支持不够全面,缺少对某些特性的支持(如标签和插件系统)。
    • 顺序保留问题: 默认情况下,加载YAML时不保证元素的原始顺序,可能导致顺序敏感的数据丢失其排列信息。
    • 安全性考虑 : yaml.load()函数可能存在潜在的安全风险(如代码注入),推荐使用yaml.safe_load()替代。

ruamel.yaml

  • 优点:

    • 先进功能: 支持YAML 1.2规范,提供比PyYAML更丰富的功能,如保留注释、原始标记、精确的缩进控制等。
    • 顺序保留: 读取YAML时能准确保持元素的原始顺序,适用于对顺序敏感的数据。
    • 多文档支持: 可以轻松处理包含多个独立YAML文档的文件。
    • 安全性增强: 提供了更安全的加载机制,降低代码注入的风险。
  • 缺点:

    • 学习曲线较陡: API相对于PyYAML来说稍显复杂,初学者可能需要更多时间熟悉。
    • 性能影响: 由于提供了额外的特性支持,如注释保留等,可能在处理大型文件时比PyYAML稍慢。
    • 依赖关系 : 一些高级功能可能需要额外的库支持,如颜色高亮输出需要colorama库。

yamlize

  • 优点:

    • 类型安全: 提供了基于数据类(dataclasses)的序列化和反序列化,确保类型安全和自动转换。
    • 易于定制: 用户可以自定义数据类以匹配特定的YAML结构,简化了复杂数据模型的处理。
    • 代码整洁: 通过装饰器声明方式简化了代码,使得业务逻辑和YAML处理分离,提高代码可读性和维护性。
  • 缺点:

    • 应用场景受限: 更适合处理具有严格结构定义的数据模型,对于松散结构或灵活的数据表示可能不如其他库便捷。
    • 功能相对有限: 相对于PyYAML和ruamel.yaml,yamlize提供的功能相对较少,可能不适合复杂的YAML处理需求。
    • 社区活跃度: 相比于前两者,yamlize的社区活跃度和用户基数较小,遇到问题可能求助资源较少。

各库适用场景总结

总结起来,选择哪个库取决于具体的项目需求:

  • 如果需要一个简单、通用且广泛使用的库,PyYAML可能是首选。
  • 若项目涉及复杂的YAML结构、需要保留注释或精确控制序列化细节,ruamel.yaml更适合。
  • 对于注重类型安全、希望基于数据类进行序列化/反序列化的场景,yamlize提供了便利的解决方案。

四、功能实现

基于第二章节的需求分析和第三章节的技术选型,我最终选择的是ruamel.yaml

最终代码呈现如下: 基类:

python 复制代码
class BaseYAMLManager:
    def __init__(self, file_path: str, auto_save: bool = False):
        self.file_path = file_path
        self.auto_save = auto_save

    def save(self, file_path: str = None, encoding='utf-8'):
        pass

    def load(self, file_path: str = None, encoding='utf-8'):
        pass

    def rollback(self, previous_documents):
        pass

    def backup(self):
        pass

    def insert_document(self, new_content: dict, position: int):
        pass

    def update_document(self, position: int, updated_content: dict):
        pass

    def delete_document(self, position: int):
        pass

    def get_document(self, position: int):
        pass

    def get_documents(self):
        pass

装饰器:

python 复制代码
def with_auto_save_rollback(method):
    @wraps(method)
    def wrapper(file_manager: BaseYAMLManager, *args, **kwargs) -> Any:
        previous_documents = file_manager.backup()  # Backup each document individually

        try:
            result = method(file_manager, *args, **kwargs)

            if file_manager.auto_save:
                file_manager.save()

            return result
        except Exception as e:
            file_manager.rollback(previous_documents)
            raise e

    return wrapper

YAMLFileManager类:

python 复制代码
from functools import wraps
from types import NoneType
from typing import Union, Any, Dict, List, Tuple, Set
import copy
import ruamel.yaml
from .base import BaseYAMLManager

YAMLValue = Union[
    Dict[str, Any],
    List[Any],
    Tuple[Any, ...],
    Set[Any],
    bool,
    str,
    int,
    float,
    NoneType,
]


class YAMLDocument(BaseYAMLManager):
    def __init__(self, content: dict, position: int = None):
        self.content = content
        self.position = position
        self._backup_content = None

    def backup(self):
        self._backup_content = copy.deepcopy(self.content)

    def restore_from_backup(self):
        if self._backup_content is not None:
            self.content = self._backup_content
            self._backup_content = None


class YAMLFileManager:
    def __init__(self, file_path: str, auto_save: bool = False):
        super().__init__(file_path, auto_save)
        self.file_path = file_path
        self.documents = []
        self._auto_save = auto_save
        self.load()

    def load(self, file_path: str = None, encoding='utf-8'):

        """
        加载YAML文件,保留注释和原始格式
        :param file_path: 加载的yaml文件路径,若不传,则默认使用当前对象的file_path
        :param encoding: 文件编码格式
        :return:
        """
        file_path = file_path or self.file_path
        with open(file_path, 'r', encoding=encoding) as f:
            yaml = ruamel.yaml.YAML()
            yaml.preserve_quotes = True
            yaml.constructor.add_constructor(
                None, lambda loader, node: loader.construct_mapping(node, deep=True))
            yaml.representer.add_representer(
                type(None), lambda dumper, data: dumper.represent_scalar('tag:yaml.org,2002:null', ''))
            for i, doc in enumerate(yaml.load_all(f)):
                self.documents.append(YAMLDocument(content=doc, position=i))

    def save(self, file_path: str = None, encoding='utf-8'):
        """
        保存YAML文件,精确控制输出格式
        :param file_path: 输出yaml文件路径,若不传,则使用当前对象的file_path
        :param encoding: 文件编码格式
        :return:
        """
        file_path = file_path or self.file_path
        with open(file_path, 'w', encoding=encoding) as f:
            yaml = ruamel.yaml.YAML()
            yaml.indent(mapping=2, sequence=4, offset=2)
            yaml.explicit_start = True
            yaml.explicit_end = True
            yaml.preserve_quotes = True
            yaml.width = 79
            yaml.allow_unicode = True
            yaml.default_flow_style = False
            yaml.sort_base_mapping_type_on_output = False
            for doc in self.documents:
                yaml.dump(doc.content, f)
                if doc.position < self.documents[-1].position:  # Not the last document, add separator
                    f.write('\n---\n')

    def rollback(self, previous_documents):
        for prev_doc, curr_doc in zip(previous_documents, self.documents):
            curr_doc.restore_from_backup()

    def backup(self):
        return [doc.backup() for doc in self.documents]

    @with_auto_save_rollback
    def insert_document(self, new_content: dict, position: int):
        """在指定位置插入新文档"""
        self.documents.insert(position, YAMLDocument(new_content, position))

    @with_auto_save_rollback
    def update_document(self, position: int, updated_content: dict):
        """更新指定位置的文档"""
        self.documents[position].content = updated_content

    @with_auto_save_rollback
    def delete_document(self, position: int):
        """删除指定位置的文档"""
        # del self.documents[position]
        self.documents.pop(position)

    def get_documents(self) -> List[YAMLDocument]:
        """获取所有文档"""
        return self.documents

    def get_document(self, position: int) -> YAMLDocument:
        """获取指定位置的文档"""
        return self.documents[position]

    @property
    def auto_save(self):
        return self._auto_save

    @auto_save.setter
    def auto_save(self, auto_save: bool):
        self._auto_save = auto_save

五、附录

关于yaml介绍和语法部分参考了这位老哥的文章 Python中yaml的使用方法

相关推荐
_.Switch22 分钟前
Python 自动化运维持续优化与性能调优
运维·开发语言·python·缓存·自动化·运维开发
J不A秃V头A28 分钟前
Python爬虫:获取国家货币编码、货币名称
开发语言·爬虫·python
阿斯卡码2 小时前
jupyter添加、删除、查看内核
ide·python·jupyter
埃菲尔铁塔_CV算法4 小时前
图像算法之 OCR 识别算法:原理与应用场景
图像处理·python·计算机视觉
封步宇AIGC5 小时前
量化交易系统开发-实时行情自动化交易-3.4.2.Okex行情交易数据
人工智能·python·机器学习·数据挖掘
封步宇AIGC5 小时前
量化交易系统开发-实时行情自动化交易-2.技术栈
人工智能·python·机器学习·数据挖掘
love_and_hope6 小时前
Pytorch学习--神经网络--完整的模型训练套路
人工智能·pytorch·python·深度学习·神经网络·学习
在人间负债^6 小时前
基于标签相关性的多标签学习
人工智能·python·chatgpt·大模型·图像类型
python1567 小时前
使用YOLOv9进行图像与视频检测
开发语言·python·音视频
狂奔solar7 小时前
DQN强化训练agent玩是男人就下xx层小游戏
python·pygame·dqn 强化