标准标签的 PyYAML 替代构造函数

PyYAML alternative constructors for standard tags

在尝试创建一个能够扩展变量的加载器时,我注意到当我使用块标量时 PyYAML 没有扩展变量(例如 >-)。所以,举个例子:

# ademo.yml
key1: foo
key2: bar $LOL bar
key3: >-
    baz $ASD baz

为了扩展变量,我做了以下操作:

# yaamer.py
import re
import sys
import yaml

# I create a loder
class MyLoader(yaml.SafeLoader): pass

# Add to it an implicit resolver that matches variables
matcher = re.compile(r".*$[A-Z].*")
MyLoader.add_implicit_resolver("!path", matcher, None)

# Add a constructor that would replaces the variable
def cons(loader, node):
    print("Constructing for", node)
    return "FOO"
MyLoader.add_constructor("!path", cons)

with open(sys.argv[1]) as inf:
    print(yaml.load(inf, MyLoader))

当我调用它时,python yaamer.py ademo.yml,我得到这个输出:

Constructing for ScalarNode(tag='!path', value='bar $LOL bar')
{'key1': 'foo', 'key2': 'FOO', 'key3': 'baz $ASD baz'}

所以,替换只是在key2,而不是key3。

为了了解发生了什么,我为正则表达式实现了一个包装器,让我了解发生了什么:

class Matcher:
    def __init__(self):
        self.matcher = re.compile(r".*$[A-Z].*")                                                                 
    def match(self, *args, **kwargs):
        print("Matching", args, kwargs)
        return self.matcher.match(*args, **kwargs)

并使用它会导致此输出:

Matching ('key1',) {}
Matching ('foo',) {}
Matching ('key2',) {}
Matching ('bar $LOL bar',) {}
Matching ('key3',) {}
Constructing for ScalarNode(tag='!path', value='bar $LOL bar')
{'key1': 'foo', 'key2': 'FOO', 'key3': 'baz $ASD baz'}

匹配中没有块标量的踪迹!因此,显然,这个匹配器从未用于解析 >- 块。

于是我调查了一下,发现有一些pre-defined tags,比如!!str或者!!map。我虽然如此,因为 >- 是一种特殊语法,它会自动匹配到其中一个标签。

所以我继续为所有标签安装自定义构造函数,以查看是否调用了其中的任何一个:

def make_cons(name):
    def _cons(loader, node):
        print("Constructor for", name)
        return node.value
    return _cons

MyLoader.add_constructor("!!str", make_cons('!!str'))
MyLoader.add_constructor("!!int", make_cons('!!int'))

但是他们从未被调用过!到底是怎么回事??如果我必须为预先存在的 YAML 标签安装自定义构造函数,我该怎么做?

隐式解析器只能匹配普通标量,而不是引用。带引号的意思是单引号或双引号,或者文字或折叠块标量。所以你的 key3 是一个折叠块标量,因此不考虑匹配。

您可以在 YAML 源代码中显式添加 !path 标签,然后您的自定义构造函数将被调用。

查看 PyYAML Documentation 并查找单词 'implicit' 和 'plain'。

要为标准标签添加构造函数,您需要使用全名。 !!str 只是 tag:yaml.org,2002:str 的 shorthand:

yaml.add_constructor('tag:yaml.org,2002:str', my_constructor)