Python处理yaml和嵌套数据结构技巧示例

引言

分享一些用Python处理yaml和嵌套数据结构的的一些技巧,首先从修改yaml格式文件的问题出发,演变出了各个解决办法,又从最后的解决办法中引申出了普适性更强的嵌套数据结构的定位方法。

  • 保留注释修改yaml文件
  • 定位嵌套数据结构
  • 定位嵌套数据结构2

保留注释修改yaml文件

yaml比之json文件的其中一个区别就是可以注释,这些注释有时候是很重要的内容,就像代码中的注释一样,如果是手动编辑自然是没有问题的,那么如何在保留注释的情况下用代码修改yaml文件呢?

假设我们要修改的yaml文件如下:

# 主要维护人
name: zhangsan

# 各集群运维人员
cluster1:
  node1:
    tomcat: user11

cluster2:
  node1:
    tomcat: user21

不保留注释

为了演示处理yaml的各个方法,这里把不保留注释的方法也纳入到本文了。

def ignore_comment():
    data = yaml.load(text, Loader=yaml.Loader)
    data["name"] = "wangwu"
    print(yaml.dump(data))

输出如下:

cluster1:
  node1:
    tomcat: user11
cluster2:
  node1:
    tomcat: user21
name: wangwu

很显然,这不是我们要的结果, 那么就淘汰这个方法吧。

此方法只适用于不需要保留注释的修改。

正则表达式

既然load, dump方法会丢弃注释,那么用正则表达式不就可以了么,处理文本一定有正则表达式一席之地的。

假设还是将name: zhangsan改成name: wangwu。

def regex1():
    pattern = "name:\s+\w+"
    pat = re.compile(pattern=pattern)
    # 首先匹配到对应的字符串
    sub_text = pat.findall(text)[0]
    # 根据这个字符串找到在文本的位置
    start_index = text.index(sub_text)
    # 根据起始位置计算结束位置
    end_index = start_index + len(sub_text)
    print(start_index, end_index, text[start_index:end_index])

    # 将根据索引替换内容
    replace_text = "name: wangwu"
    new_text = text[:start_index] + replace_text + text[end_index:]
    print("="*10)
    print(new_text)

输出如下:

8 22 name: zhangsan
==========
# 主要维护人
name: wangwu
# 各集群运维人员
cluster1:
  node1:
    tomcat: user11
cluster2:
  node1:
    tomcat: user21

看起来不错,好像能够满足需求,但是这里有一个问题就是,假设修改是cluster2.node1.tomcat的值呢?

因为文本中有两个tomcat的值,所以只是通过正则表达式不能一击即中,需要多一些判断条件,比如首先找到cluster2的起始位置,然后过滤掉小于这个起始位置的索引值,但是如果还有cluster3,cluster4呢?总的来说还 是需要人工的过一遍,然后根据观察结果来编写正则表达式,但是这样太不智能,太不自动了。

此方法适用于比较容器匹配的文本。

语法树

其实整个文本的数据结构大致如下:

无论是编程语言还是数据文本,如json, yaml, toml都可以得到这样的语法树,通过搜索这颗语法树,我们就能找到对应的键值对。

def tree1():
    tree = yaml.compose(text)
    print(tree)

输出如下:

MappingNode(tag='tag:yaml.org,2002:map', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='name'), ScalarNode(tag='tag:yaml.org,2002:str', value='zhangsan')), (ScalarNode(tag='tag:yaml.org,2002:str', value='cluster1'), MappingNode(tag='tag:yaml.org,2002:map', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='node1'), MappingNode(tag='tag:yaml.org,2002:map', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='tomcat'), ScalarNode(tag='tag:yaml.org,2002:str', value='user11'))]))])), (ScalarNode(tag='tag:yaml.org,2002:str', value='cluster2'), MappingNode(tag='tag:yaml.org,2002:map', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='node1'), MappingNode(tag='tag:yaml.org,2002:map', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='tomcat'), ScalarNode(tag='tag:yaml.org,2002:str', value='user21'))]))]))])

通过yaml.compose方法我们就能得到一颗节点树,并且每个节点会包括该节点的文本信息,比如起始,终止的文本索引。

通过观察我们能找到name: zhangsan的两个节点, 键name是一个ScalarNode节点, 值zhangsan也是一个ScalarNode, 所以我们可以打印一下看看是否和正则表达式的结果一致。

def tree2():
    tree = yaml.compose(text)
    key_name_node = tree.value[0][0]
    value_name_node = tree.value[0][1]
    print(key_name_node.start_mark.pointer, value_name_node.end_mark.pointer, key_name_node.value, value_name_node.value)

输出如下:

8 22 name zhangsan

结果与正则表达式一致,所以说明这种方法可行并且准确。

得到了修改文本的索引位置,就可以替换了,这里就不再演示了。

此方法适合保留注释的修改,并且定位嵌套结构较之正则表达式要简单,并且不需要人工介入。

那么如何定位嵌套结构呢?

定位嵌套数据结构

从上一节我们了解到了数据结构可以抽象成一颗语法树, 那么利用一些树的搜索算法就可以定位到目标文本了。

这里展示一下不包含列表节点的搜索算法。

def find_slice(tree: yaml.MappingNode, keys: List[str]) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    找到yaml文件中对应键值对的索引, 返回一个((key起始索引, key结束索引+1), (value起始索引, value结束索引+1))的元组
    暂时只支持键值对的寻找.

    比如:
    >>> find_slice("name: zhangsan", ["name"])
    ((0, 4), (6, 14))
    """
    if isinstance(tree, str):
        tree = yaml.compose(tree, Loader=yaml.Loader)
    assert isinstance(tree, yaml.MappingNode), "未支持的yaml格式"
    target_key = keys[0]
    for node in tree.value:
        if target_key == node[0].value:
            key_node, value_node = node
            if len(keys) == 1:
                key_pointers = (key_node.start_mark.pointer, key_node.end_mark.pointer)
                value_pointers = (value_node.start_mark.pointer, value_node.end_mark.pointer)
                return (key_pointers, value_pointers)

            return find_slice(node[1], keys[1:])

    return ValueError("没有找到对应的值")

算法核心在于递归。

这里的实现并没有处理列表节点(SequenceNode)。

假设我们要找cluster1.node1.tomcat并将其值改成changed, 代码如下:

def tree3():
    slices = find_slice(text, ["cluster1", "node1", "tomcat"])
    value_start_index, value_end_index = slices[1]
    replace_text = "changed"
    new_text = text[:value_start_index] + replace_text + text[value_end_index:]
    print(new_text)

输出如下

# 主要维护人
name: zhangsan

# 各集群运维人员
cluster1:
  node1:
    tomcat: changed

cluster2:
  node1:
    tomcat: user21

上面的算法只能定位key-value类型的数据结构,现在在此优化一下,让其 支持序列。

def find_slice2(tree: yaml.MappingNode, keys: List[str]) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    找到yaml文件中对应键值对的索引, 返回一个((key起始索引, key结束索引+1), (value起始索引, value结束索引+1))的元组
    暂时只支持键值对的寻找.

    比如:
    >>> find_slice2("name: zhangsan", ["name"])
    ((0, 4), (6, 14))
    """
    if isinstance(tree, str):
        tree = yaml.compose(tree, Loader=yaml.Loader)
    target_key = keys[0]

    assert isinstance(tree, yaml.MappingNode) or isinstance(tree, yaml.SequenceNode), "未支持的yaml格式"

    ret_key_node = None
    ret_value_node = None
    value_pointers= (-1, -1)

    if isinstance(tree, yaml.SequenceNode):
        assert isinstance(target_key, int), "错误的数据格式"
        # 索引可以是负索引, 比如[1,2,3][-1]
        if len(tree.value) < abs(target_key):
            raise IndexError("索引值大于列表长度")

        node = tree.value[target_key]
        if len(keys) > 1:
            return find_slice2(tree.value[target_key], keys[1:])

        if isinstance(node, yaml.MappingNode):
            ret_key_node, ret_value_node = node.value[0]
        else:
            ret_key_node = node

    if isinstance(tree, yaml.MappingNode):
        for node in tree.value:
            if target_key == node[0].value:
                key_node, value_node = node
                if len(keys) > 1:
                    return find_slice2(node[1], keys[1:])
                ret_key_node = key_node
                ret_value_node = value_node

    if ret_key_node:
        key_pointers = (ret_key_node.start_mark.pointer, ret_key_node.end_mark.pointer)

    if ret_value_node:
        value_pointers = (ret_value_node.start_mark.pointer, ret_value_node.end_mark.pointer)

    if ret_key_node:
        return (key_pointers, value_pointers)

    return ValueError("没有找到对应的值")

假设yaml文件如下:

# 用户列表
users:
  - user1: wangwu
  - user2: zhangsan

# 集群中间件版本
cluster:
  - name: tomcat
    version: 9.0.63
  - name: nginx
    version: 1.21.6
def tree4():
    slices = find_slice2(text2, ["cluster", 1, "version"])
    value_start_index, value_end_index = slices[1]
    replace_text = "1.22.0"
    new_text = text2[:value_start_index] + replace_text + text2[value_end_index:]
    print(new_text)

输出如下:

# 用户列表
users:
  - user1: wangwu
  - user2: zhangsan

# 集群中间件版本
cluster:
  - name: tomcat
    version: 9.0.63
  - name: nginx
    version: 1.22.0

结果符合预期。

定位嵌套数据结构2

上面介绍了如何定位嵌套的数据结构树,这一节介绍一下如何定位较深的树结构(主要指python字典)。

链式调用get

在获取api数据的时候因为想要的数据结构比较深,用索引会报错,那么就 需要捕获异常,这样很麻烦,并且代码很冗长,比如:

data1 = {"message": "success", "data": {"limit": 0, "offset": 10, "total": 100, "data": ["value1", "value1"]}}
data2 = {"message": "success", "data": None}
data3 = {"message": "success", "data": {"limit": 0, "offset": 10, "total": 100, "data": None}}

上面的数据结构很有可能来自同一个api结构,但是数据结构却不太一样。

如果直接用索引,就需要捕获异常,这样看起来很烦,那么可以利用字典的get方法。

ret = data1.get("data", {}).get("data", [])
if ret:
    pass # 做一些操作
if data2.get("data"):
    ret = data2["data"].get("data", [])
ret = data3.get("data", {}).get("data", [])

通过给定一个预期的数据空对象,让get可以一致写下去。

写一个递归的get

起始在之前的find_slice方法中,我们就发现递归可以比较好的处理这种嵌套的数据结构,我们可以写一个递归处理函数,用来处理很深的数据结构。

假设数据结构如下:

data = {"message": "success", "data": {"data": {"name": "zhangsan", "scores": {"math": {"mid-term": 88, "end-of-term": 90}}}}}

我们的目标就是获取数据中张三期中数学成绩: 88

实现的递归调用如下:

def super_get(data: Union[dict, list], keys: List[Union[str, int]]):
    assert isinstance(data, dict) or isinstance(data, list), "只支持字典和列表类型"
    key = keys[0]

    if isinstance(data, list) and isinstance(key, int):
        try:
            new_data = data[key]
        except IndexError as exc:
            raise IndexError("索引值大于列表长度") from exc
    elif isinstance(data, dict) and isinstance(key, str):
        new_data = data.get(key)
    else:
        raise ValueError(f"数据类型({type(data)})与索引值类型(f{type(key)}不匹配")

    if len(keys) == 1:
        return new_data

    if not isinstance(new_data, dict) and not isinstance(new_data, list):
        raise ValueError("找不到对应的值")
    return super_get(new_data, keys[1:])

然后执行代码:

def get2():
    data = {"message": "success", "data": {"data": {"zhangsan": {"scores": {"math": {"mid-term": 88, "end-of-term": 90}}}}}}
    print(super_get(data, ["data", "data", "zhangsan", "scores", "math", "mid-term"]))
    # 输出 88
    data = {"message": "success", "data": {"data": {"zhangsan": {"scores": {"math": [88, 90]}}}}}
    print(super_get(data, ["data", "data", "zhangsan", "scores", "math", 0]))
    # 输出 88
    data = {"message": "success", "data": {"data": {"zhangsan": {"scores": {"math": [88, 90]}}}}}
    print(super_get(data, ["data", "data", "zhangsan", "scores", "math", -1]))
    # 输出 90 

第三方库

其实有语法比较强大的库,比如jq, 但是毕竟多了一个依赖,并且需要一定的学习成本,但是,如果确定自己需要更多的语法,那么可以去安装一下第三方库。

总结

如果遇到较深的嵌套,递归总能很好的解决,如果实在想不出比较好的算法,那就找个第三方库吧,调库嘛,不寒碜。

源码地址:https://github.com/youerning/blog/tree/master/py_yaml_nested_data

以上就是Python处理yaml和嵌套数据结构技巧示例的详细内容,更多关于Python处理yaml嵌套数据结构的资料请关注我们其它相关文章!

(0)

相关推荐

  • Python学习之yaml文件的读取详解

    目录 yaml 文件的应用场景与格式介绍 yaml 文件的应用场景 yaml 文件的格式 第三方包 - pyyaml 读取 yaml 文件的方法 yaml文件读取演示案例 yaml 文件的应用场景与格式介绍 yaml 文件的应用场景 yaml其实也类似于 json.txt ,它们都属于一种文本格式.在我们的实际工作中, yaml 文件经常作为服务期配置文件来使用. 比如一些定义好的内容,并且不会修改的信息,我们就可以通过定义 yaml 文件,然后通过读取这样的文件,将数据导入到我们的服务中进行使

  • python 字典和列表嵌套用法详解

    python中字典和列表的使用,在数据处理中应该是最常用的,这两个熟练后基本可以应付大部分场景了.不过网上的基础教程只告诉你列表.字典是什么,如何使用,很少做组合说明. 刚好工作中采集prometheus监控接口并做数据处理的时候,用了很多组合场景,列出几个做一些分享. 列表(List) 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. 列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现

  • Python使用PyYAML库读写yaml文件的方法

    目录 一,YAML 简介 二,YAML 语法 三,安装第三方yaml文件处理库PyYAML 四,读取yaml文件 1,从yaml中读取字典 2,从yaml中读取list 3,从yaml中读取元组 4,从yaml中读取多组数据 五,写入yaml文件 1,单组数据写入yaml文件 2,多组数据写入yaml文件 Python中yaml文件的读写(使用PyYAML库).最近在搭建自动化测试项目过程中经常遇到yaml文件的读写,为了方便后续使用,决定记下笔记. 最近在搭建自动化测试项目过程中经常遇到yam

  • Python嵌套式数据结构实例浅析

    本文实例讲述了Python嵌套式数据结构.分享给大家供大家参考,具体如下: 嵌套式数据结构指的是:字典存储在列表中, 或者列表作为值存储在字典中.甚至还可以在字典中嵌套字典. 1 字典列表 列表中包含字典.比如花名册: people1 = {'name': '林旭恒', 'school': '安徽师范大学附属中学'} people2 = {'name': '吴作同', 'school': '中山市第一中学'} people_list = [people1, people2] for people

  • Python配置文件yaml的用法详解

    目录 一.PyYaml 二.yaml语法 1.基本规则 2.yaml转字典 3.yaml转列表 4.复合结构 5.基本类型 6.引用 7.强制转换 8.分段 三.构造器(constructors).表示器(representers).解析器(resolvers ) 四.示例 YAML是一种直观的能够被电脑识别的的数据序列化格式,容易被人类阅读,并且容易和脚本语言交互.YAML类似于XML,但是语法比XML简单得多,对于转化成数组或可以hash的数据时是很简单有效的. 一.PyYaml 1.loa

  • Python中的嵌套循环详情

    目录 1 什么是嵌套循环 2 Python 嵌套 for 循环 2.1 嵌套循环打印图案 2.2 在 for 循环中的while循环 2.3 实践:打印一个带有 5 行 3 列星形的矩形图案 3 打破嵌套循环 4 继续嵌套循环 5 使用列表理解的单行嵌套循环 6 Python中的嵌套while循环 6.1 While 循环内的 for 循环 7 何时在 Python 中使用嵌套循环? 1 什么是嵌套循环 所谓嵌套循环就是一个外循环的主体部分是一个内循环.内循环或外循环可以是任何类型,例如 whi

  • Python处理yaml和嵌套数据结构技巧示例

    引言 分享一些用Python处理yaml和嵌套数据结构的的一些技巧,首先从修改yaml格式文件的问题出发,演变出了各个解决办法,又从最后的解决办法中引申出了普适性更强的嵌套数据结构的定位方法. 保留注释修改yaml文件 定位嵌套数据结构 定位嵌套数据结构2 保留注释修改yaml文件 yaml比之json文件的其中一个区别就是可以注释,这些注释有时候是很重要的内容,就像代码中的注释一样,如果是手动编辑自然是没有问题的,那么如何在保留注释的情况下用代码修改yaml文件呢? 假设我们要修改的yaml文

  • python使用yaml 管理selenium元素的示例

    作者:做梦的人(小姐姐) 出处:https://www.cnblogs.com/chongyou/ 1.所有元素都在PageElement下的.yaml,如图 login_page.yaml文件: username:   dec: 登录页   type: xpath   value: //input[@class='custom-text'] password:   dec: 密码输入框   type: xpath   value: //input[@class='custom-text pas

  • python编程控制Android手机操作技巧示例

    目录 你应该拥有的东西 安装 开始 轻敲 截图 高级点击 TemplateMatching 滑动 打电话给某人 从手机下载文件到电脑 手机录屏 打开手机 发送 Whatsapp 消息 几天前我在考虑使用 python 从 whatsapp 发送消息.和你们一样,我开始潜伏在互联网上寻找一些解决方案并找到了关于twilio. 一开始,是一个不错的解决方案,但它不是免费的,我必须购买一个 twilio 电话号码.此外,我无法在互联网上找到任何可用的 whatsapp API.所以我放弃了使用 twi

  • Python 代码调试技巧示例代码

    Debug 对于任何开发人员都是一项非常重要的技能,它能够帮助我们准确的定位错误,发现程序中的 bug.python 提供了一系列 debug 的工具和包,可供我们选择.本文将主要阐述如何利用 python debug 相关工具进行 debug. 使用 pdb 进行调试 pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点.单步调试.进入函数调试.查看当前代码.查看栈片段.动态改变变量的值等.pdb 提供了一些常用的调试命令,详情见表

  • Python使用yaml模块操作YAML文档的方法

    目录 1. YAML简介 2. 语法规则 3. 文件数据结构 4. YAML数据格式示例 5. 安装yaml库 6. 读取YAML 6.1 读取键值对或嵌套键值对 6.2 读取数组类型 6.3 多文档同在一份yaml文件中时的读取方法 6.4 向YAML文档写入 6.5 更新/修改 YAML文件内容 7. 使用ruamel模块将数据转换为标准的yaml内容 1. YAML简介 YAML是可读性高,用来表达数据序列化格式的,专用于写配置文件的语言.YAML文件其实也是一种配置文件类型,后缀名是.y

  • Python实现求数列和的方法示例

    本文实例讲述了Python实现求数列和的方法.分享给大家供大家参考,具体如下: 问题: 输入 输入数据有多组,每组占一行,由两个整数n(n<10000)和m(m<1000)组成,n和m的含义如前所述. 输出 对于每组输入数据,输出该数列的和,每个测试实例占一行,要求精度保留2位小数. 样例输入 81 4 2 2 样例输出 94.73 3.41 实现代码: import math while 1: x = raw_input() x = list(x.split(" "))

  • Python实现PS滤镜的万花筒效果示例

    本文实例讲述了Python实现PS滤镜的万花筒效果.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 的一种滤镜效果,称为万花筒.也是对图像做各种扭曲变换,最后图像呈现的效果就像从万花筒中看到的一样: 图像的效果可以参考附录说明.具体Python代码如下: import matplotlib.pyplot as plt from skimage import io from skimage import img_as_float import numpy as np impor

  • Python实现PS图像明亮度调整效果示例

    本文实例讲述了Python实现PS图像明亮度调整效果.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 图像调整中的明度调整: 我们知道,一般的非线性RGB亮度调整只是在原有R.G.B值基础上增加和减少一定量来实现的,而PS的明度调整原理还得从前面那个公式上去找.我们将正向明度调整公式: RGB = RGB + (255 - RGB) * value / 255 转换为 RGB = (RGB * (255 - value) + 255 * value) / 255, 如果val

  • Python实现PS滤镜碎片特效功能示例

    本文实例讲述了Python实现PS滤镜碎片特效功能.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 滤镜中的碎片特效,这个特效简单来说就是将图像在 上,下,左,右 四个方向做平移,然后将四个方向的平移的图像叠加起来做平均.具体的效果图与说明可参考附录说明 from skimage import img_as_float import matplotlib.pyplot as plt from skimage import io file_name='D:/Visual Eff

  • Python实现PS图像调整黑白效果示例

    本文实例讲述了Python实现PS图像调整黑白效果.分享给大家供大家参考,具体如下: 这里用Python 实现 PS 里的图像调整–黑白,PS 里的黑白并不是简单粗暴的将图像转为灰度图,而是做了非常精细的处理,具体的算法原理和效果图可以参考附录说明. 比起之前的程序,对代码进行了优化,完全用矩阵运算代替了 for 循环,运算效率提升了很多.具体的代码如下: import numpy as np import matplotlib.pyplot as plt from skimage import

随机推荐