首頁 > 軟體

Python實現解析yaml組態檔的範例詳解

2022-09-18 22:00:55

楔子

前面我們介紹了 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 檔案長什麼樣子。

我們來看 yml 檔案,然後反推出相應的資料結構。首先整體是一個字典,裡面有 girl 和 other 兩個 key。其中 girl 對應一個陣列,陣列裡面每個元素都是字典,這是符合預期的。

然後 other 對應一個字典,而且這個字典內部有三個鍵值對,key 分別是:古明地覺、古明地戀、霧雨魔理沙,各自對應的 value 又是一個字典(內部有 length、nickname 兩個 key,length 對應整型、nickname 對應列表)。

最後再看一個本人之前專案中的 yml 檔案,可以猜猜看解析出來長什麼樣子。

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

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組態檔的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com