Python实现解析yaml配置文件的示例详解

目录

楔子

前面我们介绍了 ini 格式的配置文件,本次来看看 yaml,它的表达能力相比 ini 更加的强大。yaml 文件以 .yml 结尾,在介绍它的语法结构之前我们先来看看 yaml 的一些基本规则。

  • 大小写敏感;
  • 使用缩进表示层级关系,并且缩进只能用空格、不可以使用 tab 键。缩进的空格数目不重要,只要相同层级的元素左侧对齐即可;
  • # 表示注释,# 到行尾的所有字符都会被忽略;

yaml 支持的数据结构有以下三种:

  • 字典:键值对的集合;
  • 数组:多个元素组成的集合;
  • 标量:单个、不可分割的值;

Python 解析 yaml 则是通过一个名为 pyyaml 的库,直接 pip install pyyaml 即可。

下面我们来介绍一下 yaml 的数据结构。

字典

类似于 Python 的字典,使用键值对表示:

name: satori
# 或者写成下面的形式
{name: satori}

Python 解析之后会是什么结果呢?

import yaml

config = \"\"\"
name: satori
\"\"\"

# yaml.safe_load:只解析自己信任的输入
# yaml.unsafe_load:不检测输入的安全性
print(yaml.safe_load(config))
\"\"\"
{\'name\': \'satori\'}
\"\"\"

config = \"\"\"
{name: satori}
\"\"\"
print(yaml.safe_load(config))
\"\"\"
{\'name\': \'satori\'}
\"\"\"

在 yaml 里面,字典的 value 也可以是一个字典:

info: {name: satori, address: 东方地灵殿}

Python 解析的结果如下:

import yaml

config = \"\"\"
info: {name: satori, address: 东方地灵殿}
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{
  \'info\': {\'name\': \'satori\', 
           \'address\': \'东方地灵殿\'}
}
\"\"\"

还是很简单的。

数组

一组连字符开头的行,构成一个数组。

- 古明地觉
- 古明地恋
- 雾雨魔理沙
# - 后面要有空格
# 或者写成下面的形式
[古明地觉, 古明地恋, 雾雨魔理沙]

Python 解析的结果如下:

import yaml

config = \"\"\"
- 古明地觉
- 古明地恋
- 雾雨魔理沙
\"\"\"

print(yaml.safe_load(config))
\"\"\"
[\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\']
\"\"\"

config = \"\"\"
[古明地觉, 古明地恋, 雾雨魔理沙]
\"\"\"

print(yaml.safe_load(config))
\"\"\"
[\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\']
\"\"\"

并且数组的子成员也可以是一个数组:


 – 古明地觉
 – 古明地恋
 – 雾雨魔理沙

Python 解析的结果如下:

import yaml

config = \"\"\"
-
  - 古明地觉
  - 古明地恋
  - 雾雨魔理沙
\"\"\"

print(yaml.safe_load(config))
\"\"\"
[[\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\']]
\"\"\"

# 更简洁的写法
config = \"\"\"
- [古明地觉, 古明地恋, 雾雨魔理沙]
\"\"\"

print(yaml.safe_load(config))
\"\"\"
[[\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\']]
\"\"\"

显然数组也可以放在字典中:

# 缩进对应的空格数没有要求,但是必须一样
# 对于当前这个键值对而言也可以没有缩进
girl:
    - 古明地觉
    - 古明地恋
    - 雾雨魔理沙
# 或者下面这种形式
girl: [古明地觉, 古明地恋, 雾雨魔理沙]
# 或者下面这种形式
{girl: [古明地觉, 古明地恋, 雾雨魔理沙]}

Python 解析的结果如下:

import yaml

config = \"\"\"
girl:
  - 古明地觉
  - 古明地恋
  - 雾雨魔理沙
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{\'girl\': [\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\']}
\"\"\"

# 注意:上面的 girl 对应的是数组
# 因为每个元素前面都有 -
# 但如果没有的话会发生什么?
config = \"\"\"
girl:
    古明地觉
    古明地恋
    雾雨魔理沙
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{\'girl\': \'古明地觉 古明地恋 雾雨魔理沙\'}
\"\"\"
# 我们看到整体相当于是一个字符串
# 类似于 html,之间用一个空格代替
# 因此如果内容比较长,我们可以写成多行
# 但是注意:每一行前面必须有空格

然后是一个稍微复杂的例子:

import yaml

config = \"\"\"
girl:
    # 会对应一个数组
    - 古明地觉
    - 古明地恋
    - 雾雨魔理沙
    
place1:
    # 虽然不是数组,但是内部是字典的形式
    # 所以会对应一个含有三个键值对的字典
    古明地觉: 东方地灵殿
    古明地恋: 东方地灵殿
    雾雨魔理沙: 魔法森林

place2:
  # 是数组,数组里面每个元素是一个字典
  - 古明地觉: 东方地灵殿
  - 古明地恋: 东方地灵殿
  - 雾雨魔理沙: 魔法森林
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{
    \'girl\': [\'古明地觉\', \'古明地恋\', \'雾雨魔理沙\'],
    \'place1\': {\'古明地觉\': \'东方地灵殿\', 
               \'古明地恋\': \'东方地灵殿\', 
               \'雾雨魔理沙\': \'魔法森林\'},
    \'place2\': [{\'古明地觉\': \'东方地灵殿\'}, 
               {\'古明地恋\': \'东方地灵殿\'}, 
               {\'雾雨魔理沙\': \'魔法森林\'}]
}
\"\"\"

place1 对应的是一个字典,place2 对应的是一个数组。

标量

标量属于最基本的、不可再分的值,比较简单,我们就全部都说了吧。

import yaml

config = \"\"\"
int: 123
float: 3.14
bool:
    - true 
    - false
# 波浪号表示空    
NoneType: ~  
datetime: 2020-11-11 12:12:13

# 使用两个 ! 可以进行类型强转
# 不过几乎用不到 
cast:
    - !!str 123
    - !!str true  
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{
    \'int\': 123, \'float\': 3.14,
    \'bool\': [True, False], \'NoneType\': None,
    \'datetime\': datetime.datetime(2020, 11, 11, 12, 12, 13), 
    \'cast\': [\'123\', \'true\']
}
\"\"\"

这里可能有人已经发现了,就是字符串不需要加引号,但如果里面有特殊字符怎么办?所以 yaml 是支持使用引号括起来的。

import yaml

config = \"\"\"
name1: 古明地觉      a x   $ #  !!        
name2: \"古明地觉      a x   $ #  !!\"        
name3: \'古明地觉      a x   $ #  !!\'   
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{\'name1\': \'古明地觉      a x   $\', 
 \'name2\': \'古明地觉      a x   $ #  !!\', 
 \'name3\': \'古明地觉      a x   $ #  !!\'}
\"\"\"

对于 yaml 而言,字符串默认是从第一个不是空格的字符、匹配到最后一个不是空格的字符(如果遇到 # 直接停止)。因此如果 value 的前面或后面有空格的话,那么这些空格是不会显示的,或者当中有 #,那么 # 后面的内容也不会显示。

解决办法是使用单引号或双引号括起来,如果内部还有引号,那么需要输入两遍进行转义(如果内部的引号和外面括起来的引号相同的话)。

引用

对于 yaml 而言,还支持我们采用 & 和 * 进行引用,举个例子:

import yaml

config = \"\"\"
# 多了一个 &db_info_ref
# 相当于起了个名字,叫 db_info_ref
db_info: &db_info_ref  
    host: 127.0.0.1
    port: 5432
    user: postgres
    password: 123456

deploy:
    os: Linux
    # 将内容直接扔到里面来  
    <<: *db_info_ref  
\"\"\"

print(yaml.safe_load(config))
\"\"\"
{
    \'db_info\': {\'host\': \'127.0.0.1\',
                \'port\': 5432,
                \'user\': \'postgres\',
                \'password\': 123456},
    \'deploy\': {\'host\': \'127.0.0.1\',
               \'port\': 5432,
               \'user\': \'postgres\',
               \'password\': 123456,
               \'os\': \'Linux\'}
}
\"\"\"

& 用来建立锚点,<< 表示合并当前数据,* 表示用来引用锚点。还可以作用在数组中:

import yaml

config = \"\"\"
- &name 古明地觉 
- 古明地恋
- 雾雨魔理沙
- *name
\"\"\"

print(yaml.safe_load(config))
\"\"\"
[\'古明地觉\', \'古明地恋\', 
 \'雾雨魔理沙\', \'古明地觉\']
\"\"\"

生成 yaml 文件

既然能够读取 yaml 文件,那么自然也能生成 yaml 文件。

import yaml

data = {
    \"girl\": [
        {\"name\": \"古明地觉\", \"age\": 17, \"place\": \"东方地灵殿\"},
        {\"name\": \"古明地恋\", \"age\": 16, \"place\": \"东方地灵殿\"},
        {\"name\": \"雾雨魔理沙\", \"age\": 16, \"place\": \"魔法森林\"}
    ],
    \"other\": {
        \"古明地觉\": {\"nickname\": [\"小五\", \"少女觉\", \"觉大人\", \"小五萝莉\"],
                 \"length\": 155},
        \"古明地恋\": {\"nickname\": [\"恋恋\"], \"length\": 155},
        \"雾雨魔理沙\": {\"nickname\": [\"摸你傻\"], \"length\": 155}
    }
}

with open(\"cfg.yml\", \"w\", encoding=\"utf-8\") as f:
    yaml.dump(data, f, allow_unicode=True, indent=2)

然后我们看看生成的 yml 文件长什么样子。

Python实现解析yaml配置文件的示例详解

我们来看 yml 文件,然后反推出相应的数据结构。首先整体是一个字典,里面有 girl 和 other 两个 key。其中 girl 对应一个数组,数组里面每个元素都是字典,这是符合预期的。

然后 other 对应一个字典,而且这个字典内部有三个键值对,key 分别是:古明地觉、古明地恋、雾雨魔理沙,各自对应的 value 又是一个字典(内部有 length、nickname 两个 key,length 对应整型、nickname 对应列表)。

最后再看一个本人之前项目中的 yml 文件,可以猜猜看解析出来长什么样子。

Python实现解析yaml配置文件的示例详解

解析一下看看和你想的是不是一样的。

import yaml

with open(\".gitlab-ci.yml\", \"r\", encoding=\"utf-8\") as f:
    data = f.read()

data = yaml.safe_load(data)
print(data)
\"\"\"
{
    \'stages\': [\'test\'], 
    \'cache\': {\'key\': \'${CI_COMMIT_REF_SLUG}\', 
              \'paths\': [\'.cache/pip\']},
    \'variables\': {\'PIP_CACHE_DIR\': \'$CI_PROJECT_DIR/.cache/pip\'},
    \'test\': {\'stage\': \'test\', 
             \'image\': \'xxxxxxx/python:3.8.1-thanosclient-buster\', 
             \'only\': [\'branches\', \'tags\'],
             \'services\': [\'mysql:5.7\'],
             \'variables\': {\'PROJECT\': \'XXXXXX\', 
                           \'PIP_CACHE_DIR\': \'$CI_PROJECT_DIR/.cache/pip\',
                           \'MARKETING_CONFIG\': \'config/room/ci.cn-gz.toml\',
                           \'MYSQL_DATABASE\': \'activity\', 
                           \'MYSQL_ROOT_PASSWORD\': \'password\',
                           \'MYSQL_INITDB_SKIP_TZINFO\': \'1\'}
             }
}
\"\"\"

结果应该不难想,毕竟 yaml 文件不是很复杂。

以上就是Python实现解析yaml配置文件的示例详解的详细内容,更多关于Python解析yaml配置文件的资料请关注其它相关文章!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容