在 PyYaml 或 ruamel.yaml 中,我想知道是否有一种方法可以处理特定字符串的解析。具体来说,我希望能够将
"[inf, nan]"
解析为 [float('inf'), float('nan')]
。我还会注意到,我希望 "['inf', 'nan']"
继续解析为 ['inf', 'nan']
,所以这只是我想拦截并更改当前行为的未加引号的变体。
我知道目前我可以使用
"[.inf, .nan]"
或 "[!!float inf, !!float nan]"
,但我很好奇是否可以扩展加载器以允许我期望的语法(但没有)。
也许我只是通过允许“nan”和“inf”被解析为浮点数而不是字符串来制作一个枪 - 并且我有兴趣听到令人信服的理由,我应该不允许这种类型的解析。但我不太担心其他解析器会错误地解析我的配置的情况(但也许我低估了将来会造成的痛苦)。我计划使用它作为在命令行上解析参数的一种有效方法,并且我不希望实际的配置文件像这样编写。
无论如何,我仍然对如何做到这一点感兴趣,即使结论是不应该这样做。
基于我所看到的由
Yes
、On
、No
和 Off
造成的混乱
在 YAML 1.1 中解释为布尔值,我认为这不是一个好主意。
但是可以通过更改正则表达式在
ruamel.yaml
和 PyYAML 中执行此操作
识别浮点数(即将隐式标签 tag:yaml.org,2002:float
分配给标量)
然后确保从标量构造浮点数的例程可以处理这些额外的
标量。 ruamel.yaml
中的三个主要改进(与此相关)是:
它对于 YAML 1.1 和 YAML 1.2 解析有不同的正则表达式(后者是默认值,
前者必须通过指令指定,或者通过在 .version
实例上设置 YAML()
来指定);
各个解析器都有这些正则表达式的副本而不是共享
一个(如在 PyYAML 中,这使得在一个程序中拥有多个不同行为的解析器变得困难);
并且正则表达式编译会被延迟,直到真正需要它们为止。
鉴于差异,以下内容仅适用于
ruamel.yaml
您需要创建一个解析器,并将其正则表达式识别替换为all浮点数, 然后创建一个构造函数,根据 公认的标量:
import re, sys
import ruamel.yaml
class NanInfResolver(ruamel.yaml.resolver.VersionedResolver):
pass
# difference with the regex in resolver.py is the ? after \\.
# as well as recognising N and I as starting chars
NanInfResolver.add_implicit_resolver(
'tag:yaml.org,2002:float',
re.compile('''^(?:
[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?
|[-+]?\\.?(?:inf|Inf|INF)
|\\.?(?:nan|NaN|NAN))$''', re.X),
list('-+0123456789.niNI')
)
class NanInfConstructor(ruamel.yaml.constructor.RoundTripConstructor):
def construct_yaml_float(self, node):
value = self.construct_scalar(node).lower()
sign = +1
if value[0] == '-':
sign = -1
if value[0] in '+-':
value_s = value_s[1:]
if value == 'inf':
return sign * self.inf_value
if value == 'nan':
return self.nan_value
return super().construct_yaml_float(node)
NanInfConstructor.add_constructor(
'tag:yaml.org,2002:float', NanInfConstructor.construct_yaml_float
)
yaml_str = """\
[nano, 1.0, .NaN, inf, nan] # some extra values to test
"""
yaml = ruamel.yaml.YAML()
yaml.Resolver = NanInfResolver
yaml.Constructor = NanInfConstructor
data = yaml.load(yaml_str)
for x in data:
print(type(x), x)
print()
yaml.dump(data, sys.stdout)
给出:
<class 'str'> nano
<class 'ruamel.yaml.scalarfloat.ScalarFloat'> 1.0
<class 'float'> nan
<class 'float'> inf
<class 'float'> nan
[nano, 1.0, .nan, .inf, .nan] # some extra values to test
将
1.0
加载为 ScalarFloat
是在以下情况下保留其格式所必需的:
倾销。可以以类似的方式保留 .nan
、.inf
、nan
和 inf
的不同书写方式,但您会
必须指定一名特别代表,要么延长ScalarFloat
,要么创建一个
或者保留原始标量字符串值的更显式类型。不管怎样你
将失去使用 x is float('nan')
进行测试的可能性,这可能是一个问题
在实际程序中(这也是
这就是为什么 ruamel.yaml
在往返过程中不保留不同形式的 null 的原因)。