Формат плавает со стандартным модулем 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.



как я могу добиться этого?

615   11  

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

    Ничего не найдено.