很實用的 PyYAML 使用技巧
YAML 是一個被廣泛使用的數據序列化和配置語言,作為一個開發者,總是不免和它打交道。但處理 YAML 文檔,尤其是使用 PyYAML 的過程總是非常痛苦。
這篇文章分享我在 Python 下使用 PyYAML 的技巧和代碼片段,并介紹幾個相關的庫。
注意:本文中的代碼僅保證在 Python 3 下正常工作
總是使用?safe_load/safe_dump
PyYAML 的?load
?函數可以構造任意 Python 對象(Pickle 協議),這意味著一次?load
?可能導致任意 Python 函數被執行。
為了確保應用程序的安全性,盡量在任何情況下使用?yaml.safe_load
?和?yaml.safe_dump
。
保留字段順序
Python 3.7+ 中,dict
?keys 具備保留插入順序的特性,所以通過?yaml.safe_load
?得到的?dict
,其 keys 順序會與原始文件保持一致。
>>>?import?yaml
>>>?text?=?"""---
...?c:?1
...?b:?1
...?d:?1
...?a:?1
...?"""
>>>?d?=?yaml.safe_load(text)
>>>?d
{'c':?1,?'b':?1,?'d':?1,?'a':?1}
>>>?list(d)
['c',?'b',?'d',?'a']
當把?dict
?導出為 YAML 字符串時,為?yaml.safe_dump
?傳遞?sort_keys=False
?來保留 keys 的順序。
>>>?print(yaml.safe_dump(d))
a:?1
b:?1
c:?1
d:?1
>>>?d['e']?=?1
>>>?print(yaml.safe_dump(d,?sort_keys=False))
c:?1
b:?1
d:?1
a:?1
e:?1
如果 Python 版本較低,或者你想確保代碼能在更廣泛的環境下工作,你可以使用?oyaml?庫來代替 PyYAML 的?yaml
?包。
>>>?import?oyaml?as?yaml
>>>?d?=?yaml.safe_load(text)
>>>?d
OrderedDict([('c',?1),?('b',?1),?('d',?1),?('a',?1)])
>>>?d['e']?=?1
>>>?print(yaml.safe_dump(d,?sort_keys=False))
c:?1
b:?1
d:?1
a:?1
e:?1
優化列表項的縮進
默認情況下,PyYAML 輸出的列表縮進與其父元素一致。
>>>?d?=?{'a':?[1,?2,?3]}
>>>?print(yaml.safe_dump(d))
a:
-?1
-?2
-?3
這并不是很好的格式,根據 Ansible 和 HomeAssistant 等 YAML 書寫規范,列表項應該縮進 2 空格。
這種格式也會對導致列表項不會被如 VSCode 等編輯器識別,進而無法使用編輯器的折疊功能。
要解決這個問題,使用如下代碼片段,在代碼中定義?IndentDumper
?class:
class?IndentDumper(yaml.Dumper):
????def?increase_indent(self,?flow=False,?indentless=False):
????????return?super(IndentDumper,?self).increase_indent(flow,?False)
然后將它傳遞給?yaml.dump
?的?Dumper
?關鍵字參數。
>>>?print(yaml.dump(d,?Dumper=IndentDumper))
a:
??-?1
??-?2
??-?3
注意,
yaml.safe_dump
?由于有自己的 Dumper class,傳遞此參數會造成沖突。
輸出可讀的 UTF-8 字符
默認情況下,PyYAML 假設你希望輸出的結果里只有 ASCII 字符。
>>>?d?=?{'a':?'你好'}
>>>?print(yaml.safe_dump(d))
a:?"\u4F60\u597D"
這會讓輸出結果非常難以閱讀。
在 UTF-8 足夠普及的今天,直接輸出 UTF-8 字符是非常安全的。因此我們可以將?allow_unicode=True
?傳入?yaml.safe_dump
?使 PyYAML 將 Unicode 轉換成 UTF-8 字符串。
>>>?print(yaml.safe_dump(d,?allow_unicode=True))
a:?你好
一些 YAML 相關的庫
oyaml
Link:?https://github.com/wimglenn/oyaml
正如上文中提到的,oyaml 是?yaml
?包的替換品,使?dict
?keys 的順序在 dump/load 的時候得以保留。
oyaml 是一個單文件庫,只有 53 行代碼,因此使用起來非常靈活,你可以直接把它的代碼復制到自己的項目中,然后根據自己的需求進行修改。
strictyaml
Link:?https://github.com/crdoconnor/strictyaml
有的人說 YAML 過于復雜和靈活,不是一個好的配置語言。但我認為這不是 YAML 的問題,而是使用方式的問題。如果我們限制程序只使用 YAML 的部分功能,YAML 其實可以變得像它設計的那般好用。
這就是 StrictYAML 的設計意圖,它是一個類型安全的 YAML 解析器,實現了 YAML 規范說明中的一個子集?。
如果你對 YAML 的輸入輸出有較強的安全考慮,建議使用 StrictYAML 代替 PyYAML。
順帶一提的是,StrictYAML 的文檔站有很多關于設計細節和配置語言思考的文章,非常值得一看。
ruamel.yaml
Link:?https://yaml.readthedocs.io/en/latest/overview.html
ruamel.yaml 是 PyYAML 的一個分叉,于 2009 年發布并持續維護至今。
ruamel.yaml 的文檔里詳細說明了它和 PyYAML 的差異。總體來說,ruamel.yaml 專注在 YAML 1.2 上,對一些語法進行了優化。
ruamel.yaml 最令我感興趣的特性是輸入輸出的 “round-trip”,可以最大程度地保留輸入源的原始格式。官方文檔中的定義是這樣的:
A round-trip is a YAML load-modify-save sequence and ruamel.yaml tries to preserve, among others:
comments block style and key ordering are kept, so you can diff the round-tripped source flow style sequences ( ‘a: b, c, d’) (based on request and test by Anthony Sottile) anchor names that are hand-crafted (i.e. not of the form idNNN
)merges?in dictionaries are preserved
如果你有盡可能保留原始格式的需求,建議使用 ruamel.yaml 代替 PyYAML。
在使用中我注意到 ruamel.yaml 的 safe load 方法 (
YAML(typ='safe').load
) 與 PyYAML 有些不同,它無法解析 flow style 的集合定義 (如?a: {"foo": "bar"}
),這點沒有在文檔中提及,使用時須多加注意。
總結
YAML 有它好的地方和壞的地方。它易于閱讀,初期的學習曲線非常平緩。但 YAML 的規范說明非常復雜,不僅造成了使用中的混亂,也使不同語言的實現在很多細微的地方難以保持一致。
盡管有這些小毛病,YAML 仍然是我心中最好的配置語言。希望這篇文章所介紹的技巧能夠幫助你避免問題,獲得更好的開發和使用體驗。
(版權歸原作者所有,侵刪)