Формат плавает со стандартным модулем json
я использую стандарт модуль 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)
это работает для единственного объекта float:
>>> json.dumps(23.67, cls=MyEncoder)
'23.67'
но не для вложенных объектов:
>>> json.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'
I не хочу иметь внешних зависимостей, поэтому я предпочитаю придерживаться стандартного модуля json.
как я могу добиться этого?
11 ответов:
к сожалению, я считаю, что вы должны сделать это путем исправления обезьян (что, на мой взгляд, указывает на дефект дизайна в стандартной библиотеке
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пакета:-(.
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) return obj print simplejson.dumps(pretty_floats([23.67, 23.97, 23.87]))выдает
[23.67, 23.97, 23.87]нет monkeypatching необходимости.
Если вы используете 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 сделал число с плавающей точкой округления более последовательны. К сожалению, это не работает в 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, но ни один из них не является полностью адекватным. Манки патчинг формат JSON.шифратор.FLOAT_REPR не работает, если ваша среда выполнения Python использует версию C Модуль JSON. Класс PrettyFloat в ответе Тома Вуттке работает, но только если кодировка %g работает глобально для вашего приложения. Этот.% 15g-это немного волшебство, оно работает, потому что точность float составляет 17 значащих цифр, а %g не печатает конечные нули.
Я потратил некоторое время, пытаясь сделать PrettyFloat, что позволило настроить точность для каждого числа. Т. е. синтаксис типа
>>> json.dumps(PrettyFloat(1.0 / 3.0, 4)) '0.3333'это не так просто, чтобы получить это право. Наследование от float неудобно. Наследование от Объект и использование подкласса JSONEncoder с его собственным методом default () должны работать, за исключением того, что модуль json, похоже, предполагает, что все пользовательские типы должны быть сериализованы как строки. Ie: вы получаете строку Javascript "0.33" в выходных данных, а не число 0.33. Там может быть способ сделать эту работу, но это сложнее, чем кажется.
вы можете делать то, что вам нужно сделать, но это не задокументировано:
>>> import json >>> json.encoder.FLOAT_REPR = lambda f: ("%.2f" % f) >>> json.dumps([23.67, 23.97, 23.87]) '[23.67, 23.97, 23.87]'
Если вы застряли с Python 2.5 или более ранними версиями: трюк monkey-patch, похоже, не работает с оригинальным модулем simplejson, если установлены ускорения C:
$ 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]' >>>
очень жаль, что
dumpsне позволяет вам ничего делать, чтобы плавать. Однакоloadsделает. Поэтому, если вы не возражаете против дополнительной загрузки процессора, вы можете бросить его через кодер / декодер / кодер и получить правильный результат:>>> json.dumps(json.loads(json.dumps([.333333333333, .432432]), parse_float=lambda x: round(float(x), 3))) '[0.333, 0.432]'
решение Алекса Мартелли будет работать для однопоточных приложений, но может не работать для многопоточных приложений, которые должны контролировать количество десятичных знаков в потоке. Вот решение, которое должно работать в многопоточных приложениях:
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]'вы можете просто установить кодер.thread_local.decimal_places к числу десятичных знаков, которые вы хотите, и следующий вызов json.dumps () в этом потоке будет использовать это количество десятичных знаков
при импорте стандартного модуля 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]'
плюсы:
- работает с любым кодировщиком JSON, или даже репр python.
- короткий(иш), кажется, работает.
плюсы:
- уродливый regexp hack, едва протестирован.
квадратичную сложность.
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
Если вам нужно сделать это в python 2.7 без переопределения глобального json.шифратор.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 это не совсем работает, как Мэтью Шинкель указывает ниже:
>>> import MyEncoder >>> enc = MyEncoder() >>> enc.encode([23.67, 23.97, 23.87]) '["23.67", "23.97", "23.87"]'
Я согласен с @Nelson, что наследование от float неудобно, но, возможно, решение, которое касается только
Comments