PyYAML:YAML格式Python处理库

PyYAML 是一个用于解析和生成 YAML 格式的 Python 库。

PyYAML 相比 json,不仅能够解析数据,也能解析 Python 对象(非 YAML 标准语法)。

本文基于 PyYAML 3.13 版本。

QuickStart

安装纯 Python 实现版本

pipenv install pyyaml

PyYAML 还有一个依赖 LibYAML 的版本,处理起来比纯 Python 版本要快,可以参考文档安装。

解析 yaml:

import yaml

document = """
  a: 1
  b:
    c: 3
    d: 4
"""
print(yaml.load(document))
# {'a': 1, 'b': {'c': 3, 'd': 4}}

FAQ

没有嵌套集合的字典经过 dump 后格式错误?

如下的实例,字典中 b 字典的元素没有嵌套集合。

import yaml

d = {'a': 1, 'b': {'c': 3, 'd': 4}}
print(yaml.dump(d))

结果为:

a: 1
b: {c: 3, d: 4}

而不是我们期望的:

a: 1
b:
  c: 3
  d: 4

按照官方文档的说法,这是正常的:

By default, PyYAML chooses the style of a collection depending on whether it has nested collections. If a collection has nested collections, it will be assigned the block style. Otherwise it will have the flow style.

如果想总是输出 block style(即后者),可以在 dump 时将 default_flow_style 参数置为 False

yaml.dump(d, default_flow_style=False)

YAML → Python

yaml.load

该函数将 YAML 文档转换为 Python 对象。

注意,对不受信任的数据直接进行转换会造成安全隐患,yaml.loadpickle.load 一样强大,可以调用任意 Python 函数。对于这种情况,请使用 yaml.safe_load

>>> yaml.load("""
... - Hesperiidae
... - Papilionidae
... - Apatelodidae
... - Epiplemidae
... """)

['Hesperiidae', 'Papilionidae', 'Apatelodidae', 'Epiplemidae']

yaml.load 可以传入 字节字符串、Unicode 字符串、二进制文件对象和文本文件对象。对于 字节字符串和文件,要求必须是 utf-8utf-16-beutf-16-le编码。yaml.load 通过检查字符串和文件开头的 BOM 来检测编码,如果没有 BOM 则认为是 utf-8 编码。

yaml.load_all

如果字符串或文件包含多个 YAML 块,则可以使用 yaml.load_all 函数加载全部。

>>> documents = """
... ---
... name: The Set of Gauntlets 'Pauraegen'
... description: >
...     A set of handgear with sparks that crackle
...     across its knuckleguards.
... ---
... name: The Set of Gauntlets 'Paurnen'
... description: >
...   A set of gauntlets that gives off a foul,
...   acrid odour yet remains untarnished.
... ---
... name: The Set of Gauntlets 'Paurnimmen'
... description: >
...   A set of handgear, freezing with unnatural cold.
... """

>>> for data in yaml.load_all(documents):
...     print(data)

{'description': 'A set of handgear with sparks that crackle across its knuckleguards.\n',
'name': "The Set of Gauntlets 'Pauraegen'"}
{'description': 'A set of gauntlets that gives off a foul, acrid odour yet remains untarnished.\n',
'name': "The Set of Gauntlets 'Paurnen'"}
{'description': 'A set of handgear, freezing with unnatural cold.\n',
'name': "The Set of Gauntlets 'Paurnimmen'"}

构造任意类型 Python 对象

PyYAML允许构造任何类型的Python对象,如下,注意 None 和 Bool type 都由多种值转换而来。

>>> yaml.load("""
... none: [~, null]
... bool: [true, false, on, off]
... int: 42
... float: 3.14159
... list: [LITE, RES_ACID, SUS_DEXT]
... dict: {hp: 13, sp: 5}
... """)

{'none': [None, None], 'int': 42, 'float': 3.1415899999999999,
'list': ['LITE', 'RES_ACID', 'SUS_DEXT'], 'dict': {'hp': 13, 'sp': 5},
'bool': [True, False, True, False]}

还可以使用标记 !!python/object 构建 Python 类的实例:

>>> class Hero:
...     def __init__(self, name, hp, sp):
...         self.name = name
...         self.hp = hp
...         self.sp = sp
...     def __repr__(self):
...         return "%s(name=%r, hp=%r, sp=%r)" % (
...             self.__class__.__name__, self.name, self.hp, self.sp)

>>> yaml.load("""
... !!python/object:__main__.Hero
... name: Welthyr Syxgon
... hp: 1200
... sp: 0
... """)

Hero(name='Welthyr Syxgon', hp=1200, sp=0)

yaml.safe_load

safe_load 只识别标准 YAML 语法的 tag,不解析 Python 对象。

如果输入含有 python 对象,则会报错。

Python → YAML

yaml.dump

yaml.dump 函数接受 Python 对象并生成 YAML 文档。

>>> print yaml.dump({'name': 'Silenthand Olleander', 'race': 'Human',
... 'traits': ['ONE_HAND', 'ONE_EYE']})

name: Silenthand Olleander
race: Human
traits: [ONE_HAND, ONE_EYE]

该函数可接受第二个参数,该参数必须为文本文件对象或二进制文件对象。此时,函数将生成的 YAML 文档写入文件中,否则作为函数返回值返回。

除了普通的数据对象,PyYAML 也可以 dump Python 对象。

>>> class Hero:
...     def __init__(self, name, hp, sp):
...         self.name = name
...         self.hp = hp
...         self.sp = sp
...     def __repr__(self):
...         return "%s(name=%r, hp=%r, sp=%r)" % (
...             self.__class__.__name__, self.name, self.hp, self.sp)

>>> print(yaml.dump(Hero("Galain Ysseleg", hp=-3, sp=2)))

!!python/object:__main__.Hero {hp: -3, name: Galain Ysseleg, sp: 2}

yaml.dump_all

该函数将多个 yaml 文档 dump 到单个流中,此时传入列表或者生成器等可迭代对象。

>>> print yaml.dump([1,2,3], explicit_start=True)
--- [1, 2, 3]

>>> print yaml.dump_all([1,2,3], explicit_start=True)
--- 1
--- 2
--- 3

自定义输出格式

yaml.dump 支持许多关键字参数,用于指定输出的格式详细信息。

  • width 设置行宽
  • indent 设置缩进
  • default_flow_style 默认使用 flow style,不使用 block style。
  • default_style 设置字符串的表示符号,单引号还是双引号。

例如,设置行宽和缩进。

>>> print(yaml.dump(range(50), width=50, indent=4))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
    28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

自定义 Python 对象转换

yaml.YAMLObject

你可以控制 Python 对象拥有自己的输出格式,最简单的方式是定义一个子类从 yaml.YAMLObject 继承。

yaml.YAMLObject 通过元类“魔法”,注册一个 constructor,用于转换 YAML 节点到 Python 类实例;一个 representer,用于转换 Python 类实例到 YAML 节点。

>>> class Monster(yaml.YAMLObject):
...     yaml_tag = u'!Monster'
...     def __init__(self, name, hp, ac, attacks):
...         self.name = name
...         self.hp = hp
...         self.ac = ac
...         self.attacks = attacks
...     def __repr__(self):
...         return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
...             self.__class__.__name__, self.name, self.hp, self.ac, self.attacks)

上面的类在 dump 时

>>> print(yaml.dump(Monster(
...     name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT'])))

!Monster
ac: 16
attacks: [BITE, HURT]
hp: [3, 6]
name: Cave lizard

同时如果 load 一个 Monster 对象,YAML 格式为:

>>> yaml.load("""
... --- !Monster
... name: Cave spider
... hp: [2,6]    # 2d6
... ac: 16
... attacks: [BITE, HURT]
... """)

Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])

yaml.add_constructor 和 yaml.add_representer

如果不想使用元类继承的方式,也可以使用函数 yaml.add_constructor 注册构造函数,使用 yaml.add_repersenter 注册表现函数。

举例说明,我们有这样一个类

>>> class Dice(tuple):
...     def __new__(cls, a, b):
...         return tuple.__new__(cls, [a, b])
...     def __repr__(self):
...         return "Dice(%s,%s)" % self

>>> print(Dice(3,6))
Dice(3,6)

默认情况下转换为 YAML 格式后,不够优雅

>>> print(yaml.dump(Dice(3,6)))

!!python/object/new:__main__.Dice
- !!python/tuple [3, 6]

假如我们想让 Dice(3, 6) 在 YAML 中表示为 3d6,可以这样:

import yaml

def dice_representer(dumper, data):
    return dumper.represent_scalar('!dice', '%sd%s' % data)

# 注册表现函数
yaml.add_representer(Dice, dice_representer)

此时再 dump 一个 Dice 对象

>>> print(yaml.dump({'gold': Dice(10,6)}))
{gold: !dice '10d6'}

对于 load 我们也可以做相同的自定义:

def dice_constructor(loader, node):
...     value = loader.construct_scalar(node)
...     a, b = map(int, value.split('d'))
...     return Dice(a, b)

>>> yaml.add_constructor('!dice', dice_constructor)

此时再 load 一个 YAML 节点:

>>> print(yaml.load("""
... initial hit points: !dice 8d4
... """))

{'initial hit points': Dice(8,4)}

yaml.add_implicit_resolver

如果你想在不指定 !dice 标记的情况下将 XdY 的格式转换为 Dice 类的对象,可以使用 add_implicit_resolver

>>> import re
>>> pattern = re.compile(r'^\d+d\d+$')
>>> yaml.add_implicit_resolver(u'!dice', pattern)

这时转换就不再需要指定 !dice

>>> print(yaml.dump({'treasure': Dice(10,20)}))

{treasure: 10d20}

>>> print(yaml.load("""
... damage: 5d10
... """))

{'damage': Dice(5,10)}

转换映射参考表

YAML tag Python type
Standard YAML tags
!!null None
!!bool bool
!!int int or long (int in Python 3)
!!float float
!!binary str (bytes in Python 3)
!!timestamp datetime.datetime
!!omap, !!pairs list of pairs
!!set set
!!str str or unicode (str in Python 3)
!!seq list
!!map dict
Python-specific tags
!!python/none None
!!python/bool bool
!!python/bytes (bytes in Python 3)
!!python/str str (str in Python 3)
!!python/unicode unicode (str in Python 3)
!!python/int int
!!python/long long (int in Python 3)
!!python/float float
!!python/complex complex
!!python/list list
!!python/tuple tuple
!!python/dict dict
Complex Python tags
!!python/name:module.name module.name
!!python/module:package.module package.module
!!python/object:module.cls module.cls instance
!!python/object/new:module.cls module.cls instance
!!python/object/apply:module.f value of f(...)

参考链接

目录