YAML 将 5e-6 加载为字符串而不是数字

Posted

技术标签:

【中文标题】YAML 将 5e-6 加载为字符串而不是数字【英文标题】:YAML loads 5e-6 as string and not a number 【发布时间】:2015-08-08 03:39:51 【问题描述】:

当我使用 YAML 从 JSON 转储中加载一个数字时,该数字被加载为字符串而不是浮点数。

我认为这个简单的例子可以解释我的问题。

import json
import yaml

In [1]: import json

In [2]: import yaml

In [3]: All = 'one':1,'low':0.000001

In [4]: jAll = json.dumps(All)

In [5]: yAll = yaml.safe_load(jAll)

In [6]: yAll
Out[6]: 'low': '1e-06', 'one': 1

YAML 将 1e-06 加载为字符串而不是数字?我该如何解决?

【问题讨论】:

Disable scientific notation in python json.dumps output的可能重复 @SiHa 这可能是避免这个问题的一种方法,但真正的问题是当你离开json.dumps() 时,YAML 应该是 JSON 和 '1e-06` 的超集一个正确的 JSON 编号,AFAICT 也是一个正确的 YAML 编号。 PyYAML 只是没有正确解析它。 @Oren,我进一步更新了我的答案,因为我提出的原始模式在匹配没有点或指数部分的数字时可能存在问题。 ruamel.yaml 可以正确解析这些 JSON 数字,无需任何额外的修补。 @Oren 只需将您的 yaml 文件从 1e-3 编辑为 1.0e-3 嗨@Koo json 是从管道自动创建的.. 【参考方案1】:

问题在于 YAML 解析器设置为匹配浮点数,如下所示:

Resolver.add_implicit_resolver(
    u'tag:yaml.org,2002:float',
    re.compile(u'''^(?:[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+][0-9]+)?
    |\\.[0-9_]+(?:[eE][-+][0-9]+)?
    |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
    |[-+]?\\.(?:inf|Inf|INF)
    |\\.(?:nan|NaN|NAN))$''', re.X),
    list(u'-+0123456789.'))

而YAML spec 将科学记数法的正则表达式指定为:

-? [1-9] ( \. [0-9]* [1-9] )? ( e [-+] [1-9] [0-9]* )?

后者使点成为可选的,它不在隐式解析器中的上述re.compile() 模式中。

浮点数的匹配可以被修复,因此它将接受带有e/E但没有小数点和没有符号的指数的浮点值(即+隐含):

import yaml
import json
import re

All = 'one':1,'low':0.000001

jAll = json.dumps(All)

loader = yaml.SafeLoader
loader.add_implicit_resolver(
    u'tag:yaml.org,2002:float',
    re.compile(u'''^(?:
     [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
    |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
    |\\.[0-9_]+(?:[eE][-+][0-9]+)?
    |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
    |[-+]?\\.(?:inf|Inf|INF)
    |\\.(?:nan|NaN|NAN))$''', re.X),
    list(u'-+0123456789.'))

data = yaml.load(jAll, Loader=loader)
print 'data', data

结果:

data 'low': 1e-06, 'one': 1

JSON 允许的数字与 YAML 1.2 规范中的正则表达式之间存在差异(关于数字中所需的点和 e 为小写)。 JSON specification 是 IMO 非常清楚的,因为它不需要在 'e/E' 之前的点,也不需要在 'e/E' 之后的符号:

PyYAML 实现部分根据 JSON 规范匹配浮点数,部分根据正则表达式匹配浮点数,并在应该有效的数字上失败。

ruamel.yaml(这是我的 PyYAML 增强版)具有这些更新的模式并且可以正常工作:

import ruamel.yaml
import json

All = 'one':1,'low':0.000001

jAll = json.dumps(All)

data = ruamel.yaml.load(jAll)
print 'data', data

带输出:

data 'low': 1e-06, 'one': 1

ruamel.yaml 还接受数字“1.0e6”,PyYAML 也将其视为字符串。

【讨论】:

如果我理解正确,这客观上是 PyYAML 中的一个错误?您是否提交了修复它的拉取请求? @MarkAmery 去年我为 PyYAML 提交了一个 PR,它重新集成了两个代码分支(Python2 和 Python3)而没有任何形式的反应那个项目目前充其量是在休眠,我不会浪费我的PyYAML 的 PR 上的时间,直到它重新唤醒。后来我分叉并继续修复(PyYAML 上也有一些出色的修复),因为我必须继续前进,不能再等待。我认为这是一个错误,因为它没有实现 YAML 是 JSON 超集的原则,也没有实现 YAML 规范中给出的确切正则表达式。通过此更改,我尝试时通过了所有现有的 PyYAML 单元测试。 @MarkAmery 明确地说,我宁愿修复我在 PyYAML 中修复的错误,然后派生额外功能的源代码(PyYAML 不接受)并保持同步。 @cyberjoac 实际上并没有。只应在解析 YAML 1.2 时应用这些规则,而不应在解析 YAML 1.1 时应用这些规则。该提交中也没有添加测试正确行为的测试。 感谢您将我指向ruamel.yaml。现在应该在 Python 生态系统 IMO 中使用而不是 PyYAML。【参考方案2】:

我认为

1.0e-1

1.0E-1

已经解决了我的问题。而我读取yaml文件的代码是这样的

import yaml


def read_config(path: str):
    """read yaml file"""
    with open(path, 'r') as f:
        data = yaml.safe_load(f)
    return data

【讨论】:

【参考方案3】:

我是使用 YAML 的新手,所以不知道什么是最好的,但也可以写

1.0e-1

1.0E-1

在我的 YAML 文件中开箱即用。也就是系数有小数(没有小数,我也有字符串)。

【讨论】:

以上是关于YAML 将 5e-6 加载为字符串而不是数字的主要内容,如果未能解决你的问题,请参考以下文章

将 YAML 加载为嵌套对象而不是 Python 中的字典

在将字符串转换为 int 之前检查字符串是不是不是数字 [重复]

将字符串转换为哈希符号的最佳方法

使用 Jinja 渲染到 JS 会产生无效的数字而不是字符串

Oracle SQL:插入失败 ORA-01722:无效数字,数据是数字而不是字符串,为啥会失败?

在Golang中读取YAML文件,而YAML文件包含unicode字符(如“a u0000b”)