표준 json 모듈로 float 형식 지정


100

나는 파이썬 2.6 의 표준 json 모듈 을 사용하여 수레 목록을 직렬화하고 있습니다. 그러나 다음과 같은 결과가 나타납니다.

>>> 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]'

나는 외부 의존성을 원하지 않으므로 표준 json 모듈을 고수하는 것을 선호합니다.

이것을 어떻게 할 수 있습니까?

답변:


80

참고 : 최신 버전의 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패키지가 설계된 방식이 아닙니다 .


10
이 솔루션은 Python의 C 버전의 JSON 인코더를 사용하는 Python 2.7에서 작동하지 않습니다.
Nelson

25
그러나 이렇게하려면 % .3f 대신 % .15g 또는 % .12g와 같은 것을 사용하십시오.
Guido van Rossum 2013 년

23
주니어 프로그래머의 코드에서이 스 니펫을 찾았습니다. 잡히지 않았다면 이것은 매우 심각하지만 미묘한 버그를 만들었을 것입니다. 이 원숭이 패치의 글로벌 영향을 설명하는이 코드에 경고를 표시해 주시겠습니까?
Rory Hart

12
완료되면 다시 설정하는 것이 좋은 위생입니다. original_float_repr = encoder.FLOAT_REPR encoder.FLOAT_REPR = lambda o: format(o, '.2f') print json.dumps(1.0001) encoder.FLOAT_REPR = original_float_repr
Jeff Kaufman

6
다른 사람들이 지적했듯이 이것은 더 이상 Python 3.6 이상에서 작동하지 않습니다. 에 몇 자리를 추가하여 23.67어떻게 .2f존중되지 않는지 확인하십시오 .
Nico Schlömer

57
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]

몽키 패칭이 필요하지 않습니다.


2
이 솔루션이 마음에 듭니다. 더 나은 통합 및 2.7과 함께 작동합니다. 어쨌든 직접 데이터를 구축하고 있기 때문에 pretty_floats함수를 제거 하고 단순히 다른 코드에 통합했습니다.
mikepurvis 2012

1
Python3에서는 "Map object is not JSON serializable" 오류가 발생하지만 다음을 사용하여 map ()을 목록으로 변환 할 수 있습니다.list( map(pretty_floats, obj) )
Guglie

1
@Guglie :의 파이썬에서 3 개 때문에 map반환 반복자가 아닌list
Azat Ibrakov

4
나를 위해 작동하지 않습니다 (Python 3.5.2, simplejson 3.16.0). % .6g 및 [23.671234556, 23.971234556, 23.871234556]로 시도했지만 여전히 정수를 인쇄합니다.
szali

27

Python 2.7을 사용하는 경우 간단한 해결책은 단순히 float를 원하는 정밀도로 명시 적으로 반올림하는 것입니다.

>>> 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 반올림을보다 일관성있게 만들었 기 때문에 작동합니다 . 불행히도 이것은 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 런타임이 JSON 모듈의 C 버전을 사용하는 경우 Monkey 패치 json.encoder.FLOAT_REPR이 작동하지 않습니다. Tom Wuttke의 답변에서 PrettyFloat 클래스는 작동하지만 % g 인코딩이 응용 프로그램에 대해 전역 적으로 작동하는 경우에만 작동합니다. % .15g는 약간의 마술입니다. float 정밀도는 17 개의 유효 숫자이고 % g는 후행 0을 인쇄하지 않기 때문에 작동합니다.

각 숫자에 대한 정밀도를 사용자 지정할 수있는 PrettyFloat를 만드는 데 시간을 보냈습니다. 즉, 다음과 같은 구문

>>> json.dumps(PrettyFloat(1.0 / 3.0, 4))
'0.3333'

이것을 바로 잡는 것은 쉽지 않습니다. float에서 상속하는 것은 어색합니다. Object에서 상속하고 자체 default () 메서드와 함께 JSONEncoder 하위 클래스를 사용하는 것은 작동해야합니다. 단, json 모듈은 모든 사용자 정의 유형이 문자열로 직렬화되어야한다고 가정하는 것 같습니다. 즉, 출력에서 ​​숫자 0.33이 아니라 자바 스크립트 문자열 "0.33"으로 끝납니다. 아직이 작업을 수행 할 방법이있을 수 있지만보기보다 어렵습니다.


JSONEncoder.iterencode 및 패턴 일치를 사용하는 Python 2.6에 대한 또 다른 접근 방식은 github.com/migurski/LilJSON/blob/master/liljson.py
Nelson

바라건대 이것은 당신의 수레 주위를 더 가볍게 전달하게 만듭니다.
Lincoln B

20

정말 유감스럽게도 dumps수레에 아무것도 할 수 없습니다. 그러나 loads그렇습니다. 따라서 여분의 CPU 부하가 마음에 들지 않으면 인코더 / 디코더 / 인코더를 통해 처리하여 올바른 결과를 얻을 수 있습니다.

>>> json.dumps(json.loads(json.dumps([.333333333333, .432432]), parse_float=lambda x: round(float(x), 3)))
'[0.333, 0.432]'

감사합니다. 이것은 정말 유용한 제안입니다. 나는 parse_floatkwarg 에 대해 몰랐다!
Anonymous

3.6에서도 작동하는 가장 간단한 제안입니다.
브렌트 파우스트

"추가 CPU로드는 신경 쓰지 마십시오"라는 문구에 유의하십시오. 직렬화 할 데이터가 많으면이 솔루션을 사용하지 마십시오. 저에게 이것 만 추가하면 사소하지 않은 계산을 수행하는 프로그램이 3 배 더 오래 걸립니다.
shaneb

11

다음은 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]

데이터를 복사하지만 둥근 부동 소수점을 사용합니다.


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]'
>>> 

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]'

5
깔끔해 보이지만 Python 3.6에서는 작동하지 않는 것 같습니다. 특히 모듈 FLOAT_REPR에서 상수를 보지 못했습니다 json.encoder.
Tomasz Gandor 19.01.23

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 ()에 대한 다음 호출은 해당 소수 자릿수를 사용합니다.


2

전역 json.encoder.FLOAT_REPR을 재정의하지 않고 Python 2.7에서이 작업을 수행해야하는 경우 한 가지 방법이 있습니다.

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"]'

4
숫자가 아니라 문자열처럼 보입니다.
Matthew Schinckel

1

장점 :

  • 모든 JSON 인코더 또는 Python의 repr과 함께 작동합니다.
  • Short (ish), 작동하는 것 같습니다.

단점 :

  • 못생긴 정규식 해킹, 거의 테스트되지 않았습니다.
  • 2 차 복잡도.

    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

1

표준 json 모듈을 가져올 때 기본 인코더 FLOAT_REPR을 변경하는 것으로 충분합니다. 실제로 Encoder 인스턴스를 가져 오거나 만들 필요가 없습니다.

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]'

때로는 파이썬이 str로 추측 할 수있는 최상의 표현을 json으로 출력하는 것도 매우 유용합니다. 이렇게하면 중요한 숫자가 무시되지 않습니다.

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]'

1

@Nelson은 float에서 상속하는 것이 어색하다는 데 동의하지만 아마도 __repr__함수를 만지는 솔루션은 용서할 수있을 것입니다. 나는 decimal필요할 때 수레를 다시 포맷하기 위해 패키지를 사용했습니다 . 장점은 이것이 repr()호출되는 모든 컨텍스트에서 작동한다는 것 입니다. 예를 들어 단순히 목록을 stdout에 인쇄 할 때도 마찬가지입니다. 또한 정밀도는 데이터가 생성 된 후 런타임에서 구성 할 수 있습니다. 단점은 물론 데이터를이 특별한 float 클래스로 변환해야한다는 것입니다 (불행하게도 monkey patch으로 보일 수 없기 때문입니다 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]]'

이것은 __repr __ ()을 사용하지 않는 내장 Python3 json 패키지에서는 작동하지 않습니다.
Ian Goldby jul.

0

numpy 사용

실제로 정말 긴 수레가있는 경우 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]'


-1

이 문제를 해결하기 위해 작은 Python 라이브러리 인 fjson을 출시 했습니다 . 설치

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
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.