如何在 PyYAML 解析器中挂接过滤器?

How can I hook a filter in PyYAML parser?

我有一个要解析的 YAML 文件。

出于多种原因,我想禁止在锚 中使用点 .,只需在解析时将其替换为 _

简单地说,我想从这里开始:

foo:
    bar.baz:
        - egg
        - spam

到那个:

foo:
    bar_baz:
        - egg
        - spam

我知道可以在生成的 Python 字典上执行这种转换,但它不是正确的位置:解析器应该抛出错误或者应该替换有问题的值。

我已经尝试子类化 Loader 以进行这种转换,但是 none 的重写函数似乎没有任何效果。

没有简单的机制来以某种挂钩的形式替换键,每个映射键都通过该挂钩传递(无论如何,您可能希望拥有比仅拥有键更多的上下文)。 有多种解决方法:

  • 您可以创建一个新的 Loader,它将拥有您自己的 Constructor 子类,对映射键进行转换。这是 IMO 正确的解决方案,因为它不会影响其他 YAML 的加载。然而,它也是更棘手的问题之一
  • 您可以为正在使用的 Loader 的映射添加一个新的构造函数,从而覆盖现有的构造函数。如果您不做任何特别的事情,这会影响以后所有 YAML 文件的加载。
  • 您可以包装现有的映射构造函数,加载您的 YAML 并将原始的移回。这不会影响进一步 YAML 文件的加载。

后者可以通过以下方式完成:

import sys
import ruamel.yaml

yaml_str = """\
foo:
    bar.baz:
        - egg
        - spam
"""


def alt_construct_mapping(self, *args, **kw):
    """replace keys with dot"""
    m = self.org_construct_mapping(*args, **kw)
    for k in m:
        if '.' in k:
            m[k.replace('.', '_')] = m.pop(k)
    return m

# backup up the constructor
ruamel.yaml.constructor.BaseConstructor.org_construct_mapping = \
    ruamel.yaml.constructor.BaseConstructor.construct_mapping

# replace the constructor
ruamel.yaml.constructor.BaseConstructor.construct_mapping = alt_construct_mapping


data = ruamel.yaml.safe_load(yaml_str)
ruamel.yaml.round_trip_dump(data, sys.stdout)

# put original constructor back
ruamel.yaml.constructor.BaseConstructor.construct_mapping = \
    ruamel.yaml.constructor.BaseConstructor.org_construct_mapping

给出:

foo:
  bar_baz:
  - egg
  - spam

这是使用 ruamel.yaml 完成的,它是 PyYAML 的增强版本,我是它的作者。对于 PyYAML,只要您的 YAML 没有任何 YAML 版本 1.2 构造,这应该也能正常工作,将 ruamel.yaml 替换为 yaml 并将 round_trip_load/dump 替换为 safe_load/dump