python扩展库之PyYAML
1. yaml详解
YAML是专门用来写配置文件的语言,非常简洁和强大,使用比json更方便。它实质上是一种通用的数据串行化格式。YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。1.1 YAML基本语法规则
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
1.2 YAML数据类型
YAML 支持以下几种数据类型:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值
YAML文件数据结构:
对象:键值对的集合(简称 "映射或字典"),键值对用冒号 “:” 结构表示,冒号与值之间需用空格分隔
数组:一组按序排列的值(简称 "序列或列表"),数组前加有 “-” 符号,符号与值之间需用空格分隔
纯量(scalars):单个的、不可再分的值(如:字符串、bool值、整数、浮点数、时间、日期、null等),None值可用null可 ~ 表示
1.2.1 YAML 对象
对象键值对使用冒号结构表示 key: value,冒号后面要加一个空格。
也可以使用 key:{key1: value1, key2: value2, ...}。
还可以使用缩进表示层级关系;
key:
child-key: value
child-key2: value2
较为复杂的对象格式,可以使用问号加一个空格代表一个复杂的 key,配合一个冒号加一个空格代表一个 value
?
- complexkey1
- complexkey2
:
- complexvalue1
- complexvalue2
意思即对象的属性是一个数组 [complexkey1,complexkey2],对应的值也是一个数组 [complexvalue1,complexvalue2]
1.2.2 YAML数组
以 - 开头的行表示构成一个数组,- 后必须有空格做间隔:
- A
- B
- C
YAML 支持多维数组,可以使用行内表示:
key: [value1, value2, ...]
一个相对复杂的例子:
companies:
-
id: 1
name: company1
price: 200W
-
id: 2
name: company2
price: 500W
意思是 companies 属性是一个数组,每一个数组元素又是由 id、name、price 三个属性构成。
数组也可以使用流式(flow)的方式表示:
{'companies': [{'id': 1, 'name': 'company1', 'price': '200W'}, {'id': 2, 'name': 'company2', 'price': '500W'}]}
1.2.3 复合结构
数组和对象可以构成复合结构,例:
languages: - Ruby - Perl - Python websites: YAML: yaml.org Ruby: ruby-lang.org Python: python.org Perl: use.perl.org
转换为 json 为:
{ languages: [ 'Ruby', 'Perl', 'Python'], websites: { YAML: 'yaml.org', Ruby: 'ruby-lang.org', Python: 'python.org', Perl: 'use.perl.org' } }
1.2.4 纯量
纯量是最基本的,不可再分的值,包括:字符串、布尔值、整数、浮点数、Null、时间、日期
使用一个例子来快速了解纯量的基本使用:
boolean: - TRUE #true,True都可以 - FALSE #false,False都可以 float: - 3.14 - 6.8523015e+5 #可以使用科学计数法 int: - 123 - 0b1010_0111_0100_1010_1110 #二进制表示 null: nodeName: 'node' parent: ~ #使用~表示null string: - 哈哈 - 'Hello world' #可以使用双引号或者单引号包裹特殊字符 - newline newline2 #字符串可以拆成多行,每一行会被转化成一个空格 date: - 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd datetime: - 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
1.2.5 引用
& 锚点和 * 别名,可以用来引用:
defaults: &defaults adapter: postgres host: localhost development: database: myapp_development <<: *defaults test: database: myapp_test <<: *defaults
相当于:
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
test:
database: myapp_test
adapter: postgres
host: localhost
& 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。
1.2.6 yaml结构嵌套举例
#字典{"name":"test_yaml","result":"success"} name: "test_yaml" result: "sucess" #列表["a","b","c"] - "a" - "b" - "c" #1.字典嵌套字典 {person1:{"name":"xiaoming","age":"18"}, person2:{"name":"xiaohong","age":"20"} } person1: name: xiaoming age: 18 person2: name: xiaohong age: 20 #通过空格来表示字典,通过换行表示嵌套 #2.字典嵌套列表 {person:["a","b","c"]} person: - "a" - "b" - "c" #通过-加上空格来表示列表,通过换行表示嵌套 #3.列表嵌套列表 #[["a","b","c"],["1","2","3"]] - - "a" - "b" - "c" - - "1" - "2" - "3" #4.列表嵌套字典 [{"username1":"test1"},{"passward1":"111","username2":"test2"}] - username1: "test1" - passward1: "111" username2: "test2" #同一级别下需要换行 #读取多个文档 --- "用户名称1": "test123" "密码": "132456" --- "用户名称1": "test456" "密码": "132456"
2. python 扩展库PyYAML详解
2.1 读写单个yaml文件
python通过open方式读取文件数据,再通过load函数将数据转化为列表或字典。函数yaml.load(stream,Loader=None)用于将一个YAML文档转换为一个python对象。yaml.load接受字节字符串、Unicoode字符串、开放的二进制文件对象或开放的文本文件对象。字符串或文件必须使用utf-8、utf-16be或utf-16-le编码。yaml5.1版本后弃用了yaml.load(file)这个用法,因为该用法不安全,5.1版本后通过FullLoader,使该函数变得更加安全。
import yaml if __name__ == "__main__": with open('./test.yaml', 'r', encoding='utf-8') as f: data = yaml.load(stream=f, Loader=yaml.FullLoader) print(data)
yaml文件的读取举例:
project_name: xxx项目名称 env: 测试环境 # 测试人员名称,作用于自动生成代码的作者,以及发送企业微信、钉钉通知的测试负责人 tester_name: xxx # 域名1 host: https://www.wanandroid.com # 域名2,支持多个域名配置 app_host: # 实时更新用例内容,False时,已生成的代码不会在做变更 # 设置为True的时候,修改yaml文件的用例,代码中的内容会实时更新 real_time_update_test_cases: False # 报告通知类型:0: 不发送通知 1:钉钉 2:企业微信通知 3、邮箱通知 4、飞书通知 # 支持同时发送多个通知,如多个,则用逗号分割, 如 1, 2 notification_type: 0 # 收集失败的用例开关,整理成excel报告的形式,自动发送,目前只支持返送企业微信通知 excel_report: False # 钉钉相关配置 ding_talk: webhook: https://oapi.dingtalk.com/robot/send?access_token=a59902a7e811f93ffe301d8326b07a2acc8aa2a864e7d61ee9fc076481ced2a6 secret: SECdea6489dfcc3b9259da943c5ae38d3530696f2fa83ac72a9ee716e9511675b9b # webhook: # secret: # 数据库相关配置 mysql_db: # 数据库开关 switch: False host: user: root password: '123456' port: 3306 # 镜像源 mirror_source: http://mirrors.aliyun.com/pypi/simple/ # 企业通知的相关配置 wechat: webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=22748687-fa3b-4e48-a5d7-0502cef422b4 email: send_user: email_host: smtp.qq.com # 自己到QQ邮箱中配置stamp_key stamp_key: # 收件人改成自己的邮箱 send_list: # 飞书通知 lark: webhook:
if __name__ == '__main__': yaml_text = r"E:\work5-2\2023-6-30\test.yaml" with open(yaml_text, encoding='utf-8') as f: #用yaml.load加载 conf = yaml.load(f,Loader=yaml.FullLoader) print(conf) #{'project_name': 'xxx项目名称', 'env': '测试环境', 'tester_name': 'xxx', 'host': 'https://www.wanandroid.com', 'app_host': None, 'real_time_update_test_cases': False, 'notification_type': 0, 'excel_report': False, 'ding_talk': {'webhook': 'https://oapi.dingtalk.com/robot/send?access_token=a59902a7e811f93ffe301d8326b07a2acc8aa2a864e7d61ee9fc076481ced2a6', 'secret': 'SECdea6489dfcc3b9259da943c5ae38d3530696f2fa83ac72a9ee716e9511675b9b'}, 'mysql_db': {'switch': False, 'host': None, 'user': 'root', 'password': '123456', 'port': 3306}, 'mirror_source': 'http://mirrors.aliyun.com/pypi/simple/', 'wechat': {'webhook': 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=22748687-fa3b-4e48-a5d7-0502cef422b4'}, 'email': {'send_user': None, 'email_host': 'smtp.qq.com', 'stamp_key': None, 'send_list': None}, 'lark': {'webhook': None}}
写入文件内容使用yaml.dump(data,f),与python的json模块类似:
import yaml if __name__ == "__main__": data = {'name': 'wmq','scores': [100, 90]} with open('./test.yaml', 'w', encoding='utf-8') as f: yaml.dump(data, f)
2.2 读写多yaml文件
读取文件使用yaml.load_all(),其中返回的是generator,可以用for循环或者next取值。
import yaml if __name__ == "__main__": with open('./test.yaml', 'r', encoding='utf-8') as f: all_data = yaml.load_all(stream=f, Loader=yaml.FullLoader) for data in all_data: print(data)
同理,将多个文件内容写入yaml文件使用yaml.dump_all()
import yaml if __name__ == "__main__": obj1 = {"name": "wmq", "age": 10} obj2 = {"name": "pkq", "age": 11} with open('./test.yaml', 'w', encoding='utf-8') as f: yaml.dump_all([obj1, obj2], f)
test.yaml文件结果如下:
#------------------test.yaml------------- age: 10 name: wmq --- age: 11 name: pkq
2.3 yaml读写封装
import os import ast import yaml.scanner class GetYamlData: """ 获取 yaml 文件中的数据 """ def __init__(self, file_dir): self.file_dir = str(file_dir) def get_yaml_data(self) -> dict: """ 获取 yaml 中的数据 :param: fileDir: :return: """ # 判断文件是否存在 if os.path.exists(self.file_dir): data = open(self.file_dir, 'r', encoding='utf-8') res = yaml.load(data, Loader=yaml.FullLoader) else: raise FileNotFoundError("文件路径不存在") return res def write_yaml_data(self, key: str, value) -> int: """ 更改 yaml 文件中的值, 并且保留注释内容 :param key: 字典的key :param value: 写入的值 :return: """ with open(self.file_dir, 'r', encoding='utf-8') as file: # 创建了一个空列表,里面没有元素 lines = [] for line in file.readlines(): if line != '\n': lines.append(line) file.close() with open(self.file_dir, 'w', encoding='utf-8') as file: flag = 0 for line in lines: left_str = line.split(":")[0] if key == left_str.lstrip() and '#' not in line: newline = f"{left_str}: {value}" line = newline file.write(f'{line}\n') flag = 1 else: file.write(f'{line}') file.close() return flag
》》》》未完待续