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
View Code

意思是 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
View Code

转换为 json 为:

{ 
  languages: [ 'Ruby', 'Perl', 'Python'],
  websites: {
    YAML: 'yaml.org',
    Ruby: 'ruby-lang.org',
    Python: 'python.org',
    Perl: 'use.perl.org' 
  } 
}
View Code

 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连接,最后使用+代表时区
yaml

1.2.5 引用

 & 锚点和 * 别名,可以用来引用:

defaults: &defaults
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  <<: *defaults

test:
  database: myapp_test
  <<: *defaults
View Code

相当于:

defaults:
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  adapter:  postgres
  host:     localhost

test:
  database: myapp_test
  adapter:  postgres
  host:     localhost
View Code

& 用来建立锚点(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"
yaml

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.load

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:
test.yaml
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}}
View Code

 写入文件内容使用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)
yaml.dump

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.load_all()

同理,将多个文件内容写入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)
yaml.dump_all()

test.yaml文件结果如下:

#------------------test.yaml-------------
age: 10
name: wmq
---
age: 11
name: pkq
test.yaml

 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
View Code

 

 

 

》》》》未完待续

posted @ 2023-08-01 11:39  enjoyzier  阅读(110)  评论(0编辑  收藏  举报