将别名作为 ruamel.yaml 中的字符串读取
Reading aliases as strings in ruamel.yaml
我有一个 YAML 文件,其中包含带通配符的字符串,例如:
hello : world
foo : *
bar : ruamel.*
传递给 ruamel.yaml.load
时失败,因为 *
星号(如果是关键字(字符串)的第一个字符)表示别名的开头。如果在此示例中 bar
的值介于两者之间,则它有效。
因为如果所有 *
-led 字符串都必须用引号保护,那么写和读都不太好,而且我的文件中不需要 anchor/alias 支持,我以为我会以某种方式在加载器中禁用它。我没有直接在 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
中是否有我错过的选项?
使用ruamel.yaml
解析YAML的步骤按照应用
结果从一个到另一个:
YAML document → scanning → parsing → composing → constructing → Python data structure
当您将文档传递给 YAML().load()
时,您会得到一个 ScannerError
,因此尝试 "fix"
在施工阶段是迟到的方式。
在令牌开头对“*”的实际检查是在 fetch_more_tokens
中的方法中完成的
scanner.py
你当然可以改变那个方法(通过子类化或 monkey-patching),
但它超过了一百行,其中大部分你必须逐字复制。
相关部分是:
# Is it an alias?
if ch == '*':
return self.fetch_alias()
并且只需将 .fetch_alias()
替换为例程即可简单得多
获取 "normal" 普通标量 (.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'>]
我终于也想出了如何直接使用 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'}
我有一个 YAML 文件,其中包含带通配符的字符串,例如:
hello : world
foo : *
bar : ruamel.*
传递给 ruamel.yaml.load
时失败,因为 *
星号(如果是关键字(字符串)的第一个字符)表示别名的开头。如果在此示例中 bar
的值介于两者之间,则它有效。
因为如果所有 *
-led 字符串都必须用引号保护,那么写和读都不太好,而且我的文件中不需要 anchor/alias 支持,我以为我会以某种方式在加载器中禁用它。我没有直接在 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
中是否有我错过的选项?
使用ruamel.yaml
解析YAML的步骤按照应用
结果从一个到另一个:
YAML document → scanning → parsing → composing → constructing → Python data structure
当您将文档传递给 YAML().load()
时,您会得到一个 ScannerError
,因此尝试 "fix"
在施工阶段是迟到的方式。
在令牌开头对“*”的实际检查是在 fetch_more_tokens
中的方法中完成的
scanner.py
你当然可以改变那个方法(通过子类化或 monkey-patching),
但它超过了一百行,其中大部分你必须逐字复制。
相关部分是:
# Is it an alias?
if ch == '*':
return self.fetch_alias()
并且只需将 .fetch_alias()
替换为例程即可简单得多
获取 "normal" 普通标量 (.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'>]
我终于也想出了如何直接使用 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'}