我在python 2.6中使用标准json module来序列化浮点列表。但是,我得到的结果如下:
>>> 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模块。
我怎样才能做到这一点?
答案 0 :(得分:70)
不幸的是,我相信你必须通过猴子修补来实现这一点(我认为这表明标准库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
包的设计方式: - (。
答案 1 :(得分:56)
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 map(pretty_floats, obj) # in Python3 do: list(map(pretty_floats, obj))
return obj
print simplejson.dumps(pretty_floats([23.67, 23.97, 23.87]))
发射
[23.67, 23.97, 23.87]
不需要monkeypatching。
答案 2 :(得分:25)
如果您使用的是Python 2.7,一个简单的解决方案就是简单地将浮动显式地舍入到所需的精度。
>>> sys.version
'2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)]'
>>> json.dumps(1.0/3.0)
'0.3333333333333333'
>>> json.dumps(round(1.0/3.0, 2))
'0.33'
这很有效,因为Python 2.7创建了float rounding more consistent。不幸的是,这在Python 2.6中不起作用:
>>> sys.version
'2.6.6 (r266:84292, Dec 27 2010, 00:02:40) \n[GCC 4.4.5]'
>>> json.dumps(round(1.0/3.0, 2))
'0.33000000000000002'
上面提到的解决方案是2.6的解决方法,但没有一个是完全足够的。如果您的Python运行时使用C版本的JSON模块,则猴子修补json.encoder.FLOAT_REPR不起作用。 Tom Wuttke的答案中的PrettyFloat类可用,但前提是%g编码适用于您的应用程序。 %。15g有点神奇,它起作用,因为浮点精度是17位有效数字而%g不打印尾随零。
我花了一些时间尝试制作一个允许为每个数字定制精度的PrettyFloat。即,像
这样的语法>>> json.dumps(PrettyFloat(1.0 / 3.0, 4))
'0.3333'
要做到这一点并不容易。继承浮动很尴尬。继承自Object并使用带有自己的default()方法的JSONEncoder子类应该可以工作,除了json模块似乎假设所有自定义类型都应序列化为字符串。即:你最终得到输出中的Javascript字符串“0.33”,而不是数字0.33。可能有一种方法可以使这项工作,但它比看起来更难。
答案 3 :(得分:12)
真的很遗憾,dumps
不允许你做任何漂浮的事情。但是loads
会这样做。因此,如果您不介意额外的CPU负载,您可以通过编码器/解码器/编码器将其丢弃并获得正确的结果:
>>> json.dumps(json.loads(json.dumps([.333333333333, .432432]), parse_float=lambda x: round(float(x), 3)))
'[0.333, 0.432]'
答案 4 :(得分:9)
如果您遇到Python 2.5或更早版本:如果安装了C加速,那么Monkey-patch技巧似乎不适用于原始的simplejson模块:
$ python
Python 2.5.4 (r254:67916, Jan 20 2009, 11:06:13)
[GCC 4.2.1 (SUSE Linux)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import simplejson
>>> simplejson.__version__
'2.0.9'
>>> simplejson._speedups
<module 'simplejson._speedups' from '/home/carlos/.python-eggs/simplejson-2.0.9-py2.5-linux-i686.egg-tmp/simplejson/_speedups.so'>
>>> simplejson.encoder.FLOAT_REPR = lambda f: ("%.2f" % f)
>>> simplejson.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'
>>> simplejson.encoder.c_make_encoder = None
>>> simplejson.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'
>>>
答案 5 :(得分:7)
您可以做您需要做的事情,但没有记录:
>>> import json
>>> json.encoder.FLOAT_REPR = lambda f: ("%.2f" % f)
>>> json.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'
答案 6 :(得分:3)
这是在Python 3中对我有用的解决方案,不需要猴子补丁:
import json
def round_floats(o):
if isinstance(o, float): return round(o, 2)
if isinstance(o, dict): return {k: round_floats(v) for k, v in o.items()}
if isinstance(o, (list, tuple)): return [round_floats(x) for x in o]
return o
json.dumps(round_floats([23.63437, 23.93437, 23.842347]))
输出为:
[23.63, 23.93, 23.84]
它复制数据,但带有四舍五入的浮点数。
答案 7 :(得分:2)
如果您需要在python 2.7中执行此操作而不覆盖全局json.encoder.FLOAT_REPR,这是一种方法。
import json
import math
class MyEncoder(json.JSONEncoder):
"JSON encoder that renders floats to two decimal places"
FLOAT_FRMT = '{0:.2f}'
def floatstr(self, obj):
return self.FLOAT_FRMT.format(obj)
def _iterencode(self, obj, markers=None):
# stl JSON lame override #1
new_obj = obj
if isinstance(obj, float):
if not math.isnan(obj) and not math.isinf(obj):
new_obj = self.floatstr(obj)
return super(MyEncoder, self)._iterencode(new_obj, markers=markers)
def _iterencode_dict(self, dct, markers=None):
# stl JSON lame override #2
new_dct = {}
for key, value in dct.iteritems():
if isinstance(key, float):
if not math.isnan(key) and not math.isinf(key):
key = self.floatstr(key)
new_dct[key] = value
return super(MyEncoder, self)._iterencode_dict(new_dct, markers=markers)
然后,在python 2.7中:
>>> from tmp import MyEncoder
>>> enc = MyEncoder()
>>> enc.encode([23.67, 23.98, 23.87])
'[23.67, 23.98, 23.87]'
在Python 2.6中,它并不像Matthew Schinckel在下面指出的那样有效:
>>> import MyEncoder
>>> enc = MyEncoder()
>>> enc.encode([23.67, 23.97, 23.87])
'["23.67", "23.97", "23.87"]'
答案 8 :(得分:2)
Alex Martelli的解决方案适用于单线程应用程序,但可能不适用于需要控制每个线程的小数位数的多线程应用程序。这是一个应该适用于多线程应用程序的解决方案:
import threading
from json import encoder
def FLOAT_REPR(f):
"""
Serialize a float to a string, with a given number of digits
"""
decimal_places = getattr(encoder.thread_local, 'decimal_places', 0)
format_str = '%%.%df' % decimal_places
return format_str % f
encoder.thread_local = threading.local()
encoder.FLOAT_REPR = FLOAT_REPR
#As an example, call like this:
import json
encoder.thread_local.decimal_places = 1
json.dumps([1.56, 1.54]) #Should result in '[1.6, 1.5]'
您只需将encoder.thread_local.decimal_places设置为您想要的小数位数,并且该线程中对json.dumps()的下一次调用将使用该小数位数
答案 9 :(得分:1)
优点:
缺点:
二次复杂性。
def fix_floats(json, decimals=2, quote='"'):
pattern = r'^((?:(?:"(?:\\.|[^\\"])*?")|[^"])*?)(-?\d+\.\d{'+str(decimals)+'}\d+)'
pattern = re.sub('"', quote, pattern)
fmt = "%%.%df" % decimals
n = 1
while n:
json, n = re.subn(pattern, lambda m: m.group(1)+(fmt % float(m.group(2)).rstrip('0')), json)
return json
答案 10 :(得分:1)
导入标准json模块时,只需更改默认编码器FLOAT_REPR即可。实际上并不需要导入或创建编码器实例。
import json
json.encoder.FLOAT_REPR = lambda o: format(o, '.2f')
json.dumps([23.67, 23.97, 23.87]) #returns '[23.67, 23.97, 23.87]'
有时输出作为json非常有用python可以用str猜测的最佳表示。这将确保不会忽略重要数字。
import json
json.dumps([23.67, 23.9779, 23.87489])
# output is'[23.670000000000002, 23.977900000000002, 23.874890000000001]'
json.encoder.FLOAT_REPR = str
json.dumps([23.67, 23.9779, 23.87489])
# output is '[23.67, 23.9779, 23.87489]'
答案 11 :(得分:1)
我同意@Nelson继承浮点数很尴尬,但也许只触及__repr__
函数的解决方案可能是可以原谅的。我最终使用decimal
包来重新格式化浮点数。好处是,这适用于调用repr()
的所有上下文,例如,当只是将列表打印到stdout时也是如此。此外,在创建数据之后,精度可以运行时配置。下行当然是您的数据需要转换为这个特殊的浮点类(不幸的是,您似乎无法使用补丁float.__repr__
)。为此,我提供了一个简短的转换功能。
代码:
import decimal
C = decimal.getcontext()
class decimal_formatted_float(float):
def __repr__(self):
s = str(C.create_decimal_from_float(self))
if '.' in s: s = s.rstrip('0')
return s
def convert_to_dff(elem):
try:
return elem.__class__(map(convert_to_dff, elem))
except:
if isinstance(elem, float):
return decimal_formatted_float(elem)
else:
return elem
用法示例:
>>> import json
>>> li = [(1.2345,),(7.890123,4.567,890,890.)]
>>>
>>> decimal.getcontext().prec = 15
>>> dff_li = convert_to_dff(li)
>>> dff_li
[(1.2345,), (7.890123, 4.567, 890, 890)]
>>> json.dumps(dff_li)
'[[1.2345], [7.890123, 4.567, 890, 890]]'
>>>
>>> decimal.getcontext().prec = 3
>>> dff_li = convert_to_dff(li)
>>> dff_li
[(1.23,), (7.89, 4.57, 890, 890)]
>>> json.dumps(dff_li)
'[[1.23], [7.89, 4.57, 890, 890]]'
答案 12 :(得分:1)
如果您实际上有很长的浮动,则可以使用numpy将其正确向上/向下取整:
import json
import numpy as np
data = np.array([23.671234, 23.97432, 23.870123])
json.dumps(np.around(data, decimals=2).tolist())
'[23.67, 23.97, 23.87]'
答案 13 :(得分:0)
我刚刚发布了(Issue #232),这是一个小的Python库,可以解决此问题。使用
安装pip install fjson
并与json
一样使用,并附加了float_format
参数:
import math
import fjson
data = {"a": 1, "b": math.pi}
print(fjson.dumps(data, float_format=".6e", indent=2))
{
"a": 1,
"b": 3.141593e+00
}
答案 14 :(得分:0)
我这样做了 :) 请注意,使用我的代码时,逗号后总是有 2 位数字
>>> json_dumps_with_two_digit_float({'a': 1.0})
'{"a": 1.00}'
我的自定义函数:
from unittest.mock import patch
import json
# We need to ensure that c encoder will not be launched
@patch('json.encoder.c_make_encoder', None)
def json_dumps_with_two_digit_float(some_object):
# saving original method
of = json.encoder._make_iterencode
def inner(*args, **kwargs):
args = list(args)
# fifth argument is float formater which will we replace
args[4] = lambda o: '{:.2f}'.format(o)
return of(*args, **kwargs)
with patch('json.encoder._make_iterencode', wraps=inner):
return json.dumps(some_object)
不要忘记在您的项目中创建一些测试,因为我的 func 与 python json 模块实现密切相关,将来可以更改。