使用标准json模块格式化浮点数

134 投票
18 回答
114933 浏览
提问于 2025-04-15 14:25

我在使用标准的 json模块,版本是python 2.6,想把一个浮点数列表转换成字符串。但是,我得到的结果是这样的:

>>> import json
>>> json.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'

我希望浮点数只保留两位小数,输出应该像这样:

>>> json.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'

我尝试自己定义一个JSON编码器类:

class MyEncoder(json.JSONEncoder):
    def encode(self, obj):
        if isinstance(obj, float):
            return format(obj, '.2f')
        return json.JSONEncoder.encode(self, obj)

这个方法对单个浮点数有效:

>>> json.dumps(23.67, cls=MyEncoder)
'23.67'

但是对于嵌套对象就不行了:

>>> json.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'

我不想依赖外部库,所以我更想使用标准的json模块。

我该怎么做才能实现这个呢?

18 个回答

37

真是太遗憾了,dumps 这个函数不能对浮点数做任何处理。不过 loads 是可以的。所以如果你不介意多消耗一点CPU资源的话,可以把数据先通过编码器,再通过解码器,最后再通过编码器,这样就能得到正确的结果:

>>> json.dumps(json.loads(json.dumps([.333333333333, .432432]), parse_float=lambda x: round(float(x), 3)))
'[0.333, 0.432]'
63
import simplejson
    
class PrettyFloat(float):
    def __repr__(self):
        return '%.15g' % self
    
def pretty_floats(obj):
    if isinstance(obj, float):
        return PrettyFloat(obj)
    elif isinstance(obj, dict):
        return dict((k, pretty_floats(v)) for k, v in obj.items())
    elif isinstance(obj, (list, tuple)):
        return list(map(pretty_floats, obj))
    return obj
    
print(simplejson.dumps(pretty_floats([23.67, 23.97, 23.87])))

发出

[23.67, 23.97, 23.87]

不需要猴子补丁。

95

注意:这个方法在任何较新版本的Python中都不管用。


很遗憾,我认为你需要通过一种叫做“猴子补丁”的方式来解决这个问题(在我看来,这说明了标准库中json包的设计缺陷)。比如,下面这段代码:

import json
from json import encoder
encoder.FLOAT_REPR = lambda o: format(o, '.2f')
    
print(json.dumps(23.67))
print(json.dumps([23.67, 23.97, 23.87]))

会输出:

23.67
[23.67, 23.97, 23.87]

正如你所希望的那样。显然,应该有一种合理的方式来重写FLOAT_REPR,这样你就可以控制每一个浮点数的表示方式;但不幸的是,json包并不是这样设计的。

撰写回答