在ruamel.yaml中以字符串形式读取别名

时间:2019-05-22 15:54:47

标签: python ruamel.yaml

我有一个YAML文件,其中包含带通配符的字符串,例如:

hello : world
foo : *
bar : ruamel.*

传递给ruamel.yaml.load时失败,因为*星号(如果关键字(字符串)的第一个字符表示别名的开头)。如果此示例中bar的值介于两者之间,则可以使用。

由于如果所有*主导的字符串都必须用引号保护,那么写和读就不太好了,而且我也不需要文件中的锚点/别名支持,所以我想d在加载程序中以某种方式禁用它。我没有直接在ruamel.yaml.Loader中找到选项,所以我仔细研究了一下代码并提出了以下建议:

from ruamel import yaml

class NoAliasLoader(yaml.Loader):
    def fetch_alias(self):                        
        return self.fetch_plain()

yaml.load(yml_doc, Loader=NoAliasLoader)

这有效,并且将值按预期解释为字符串,但前提是*后跟另一个字符,例如foo : **。如果只是星号,则会显示错误消息

ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:yaml'
  in "<unicode string>", line 3, column 7:
    foo : *
          ^ (line: 3)

仅通过遍历代码并不得不放弃,我找不到一个简单的解决方案。

那么我该如何实现自己想要的?还是我错过的某个地方的Loader中有一个选项?

2 个答案:

答案 0 :(得分:1)

使用ruamel.yaml解析YAML所涉及的步骤按应用 一个到另一个的结果:

YAML document → scanning → parsing → composing → constructing → Python data structure

将文档传递到YAML().load()时,您会得到ScannerError,因此尝试“修复”该问题 在建设阶段还很晚。


在令牌开头对'*'的实际检查是通过以下方法中的方法fetch_more_tokens完成的: scanner.py,您当然可以更改该方法(通过子类化或猴子补丁), 但这是一堆杂乱无章的文字,您必须逐字复制大部分文字。

相关部分是:

    # Is it an alias?
    if ch == '*':
        return self.fetch_alias()

用例程替换.fetch_alias()要简单得多, 提取“正常”普通标量(.fetch_plain()):

import sys
import ruamel.yaml

yaml_str = """\
hello : world
foo : *
bar : ruamel.*
"""

ruamel.yaml.scanner.Scanner.fetch_alias = ruamel.yaml.scanner.Scanner.fetch_plain
ruamel.yaml.resolver.implicit_resolvers = ruamel.yaml.resolver.implicit_resolvers[:-1]

yaml = ruamel.yaml.YAML(typ='safe', pure=True)

data = yaml.load(yaml_str)
for k in data:
    print('{:6s} -> {:10s} [{}]'.format(k, data[k], type(data[k])))

给出:

hello  -> world      [<class 'str'>]
foo    -> *          [<class 'str'>]
bar    -> ruamel.*   [<class 'str'>]

答案 1 :(得分:0)

我最终也弄清楚了如何直接使用PyYAML实现这一目标。除了将猴子fetch_alias修补到fetch_plain之外,还需要从*字典中删除yaml_implicit_resolvers键。这就是导致上述ConstructorError的原因。

import yaml
yaml.Loader.fetch_alias = yaml.Loader.fetch_plain
yaml.Loader.yaml_implicit_resolvers.pop("*", None)

结果:

yaml.load("""
hello : world
foo : *
bar : 10
""")
>>> {'bar': 10, 'foo': '*', 'hello': 'world'}