prosource

표준 json 모듈에서는 포맷이 플로팅됩니다.

probook 2023. 3. 9. 22:10
반응형

표준 json 모듈에서는 포맷이 플로팅됩니다.

저는 floats 목록을 시리얼화하기 위해 python 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)

이것은 단독 플로트 오브젝트에 대해 기능합니다.

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

그러나 중첩된 개체에 대해서는 실패합니다.

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

저는 외부 의존을 원하지 않기 때문에 표준 json 모듈을 사용하는 것을 선호합니다.

어떻게 하면 좋을까요?

주의: 이것은 최신 버전의 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이치노

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]

원숭이 패칭은 필요 없습니다.

dumps이데올로기 때문에, <고객명>님loads따라서 추가 CPU 부하가 문제가 되지 않으면 인코더/디코더/인코더를 통해 CPU 부하를 적절히 처리할 수 있습니다.

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

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.encoder에 패치를 적용합니다.Python 런타임에서 C 버전의 JSON 모듈을 사용하는 경우 FLOAT_REPR은 작동하지 않습니다.Tom Wuttke의 답변에 있는 Pretty Float 클래스는 %g 인코딩이 응용 프로그램에 대해 글로벌하게 작동하는 경우에만 작동합니다.%.15g은 약간 매직입니다.플로트 정밀도는 17자리이고 %g은 후행 0을 인쇄하지 않기 때문에 동작합니다.

각 번호의 정밀도를 커스터마이즈할 수 있는 Pretty Float을 만들기 위해 시간을 들였습니다.즉, 다음과 같은 구문

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

이걸 맞히기가 쉽지 않아요.float에서 상속하는 것은 어색합니다.개체에서 상속하여 자체 default() 메서드로 JSONEncoder 서브클래스를 사용하면 동작합니다.단, json 모듈은 모든 커스텀유형을 문자열로 시리얼화해야 한다고 가정합니다.즉, 출력에는 숫자 0.33이 아닌 Javascript 문자열 "0.33"이 표시됩니다.아직 이 일을 해낼 방법이 있을지는 모르지만, 보기보다 어려워요.

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

데이터를 복사하지만 둥근 플로트를 사용합니다.

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

필요한 작업을 수행할 수 있지만 문서화되어 있지 않습니다.

>>> import json
>>> json.encoder.FLOAT_REPR = lambda f: ("%.2f" % f)
>>> json.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'

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

저는 이 문제를 해결하기 위해 작은 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
}

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

인코더를 설정하기만 하면 됩니다.thread_local.discs_places는 원하는 소수점 이하 자리수로 배치되며, 이 스레드에서 json.discs()에 대한 다음 호출에서는 해당 소수점 이하 자리수가 사용됩니다.

글로벌 json.encoder를 덮어쓰지 않고 python 2.7에서 이 작업을 수행해야 하는 경우.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 Schinkel이 아래에 지적한 것처럼 동작하지 않습니다.

>>> import MyEncoder
>>> enc = MyEncoder()  
>>> enc.encode([23.67, 23.97, 23.87])
'["23.67", "23.97", "23.87"]'

표준 json 모듈을 Import할 때는 기본 인코더 FLOAT_REPR을 변경해도 충분합니다.인코더 인스턴스를 Import 또는 작성할 필요는 없습니다.

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로 추측할 수 있는 최적의 표현 python을 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]'

어색하다는 의 의견에 만, float에 수 있는 은 @Nelson뿐일 입니다.__repr__이 기능은 용서할 수 있습니다.는 결국 나는 the the the the the the the the the the the the the the the the the the the the the the the 를 쓰게 되었다.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]]'

새로운 답변:

대답에 영감을 받아 무섭게 보이지만 실제로는 완벽하게 작동합니다.

import json

class RoundingFloat(float):
    __repr__ = staticmethod(lambda x: format(x, '.2f'))

json.encoder.c_make_encoder = None

json.encoder.float = RoundingFloat

print(json.dumps({'number': 1.0 / 81}))

아래 오래된 답변:

TensorFlow 작성자는 regex를 사용하여 이미문제를 해결했습니다.

import json
import re

def FormatFloat(json_str, float_digits):
  pattern = re.compile(r'\d+\.\d+')
  float_repr = '{:.' + '{}'.format(float_digits) + 'f}'

  def MRound(match):
    return float_repr.format(float(match.group()))

  return re.sub(pattern, MRound, json_str)

def Dumps(obj, float_digits=-1, **params):
  """Wrapper of json.dumps that allows specifying the float precision used.

  Args:
    obj: The object to dump.
    float_digits: The number of digits of precision when writing floats out.
    **params: Additional parameters to pass to json.dumps.

  Returns:
    output: JSON string representation of obj.
  """
  json_str = json.dumps(obj, **params)

  if float_digits > -1:
    json_str = FormatFloat(json_str, float_digits)

  return json_str

이것은 포장만 하면 된다json.dumps표준 패키지에서 regex를 실행합니다.

.json 파일에서 고정 정밀도 부동 출력을 얻으려면 python_discl\lib\json 모듈의 encoder.py 모듈을 변경하는 방법이 있습니다.

처음 클래스를 만들었습니다.

        class FloatRepr(reprlib.Repr):
            def repr_float(self,value,level):
                return format(value,'.2f')

그런 다음 floatstr 함수를 다음과 같이 수정합니다.

        def floatstr(o, allow_nan=self.allow_nan, _repr=float.__repr__, _inf=INFINITY,_neginf=-INFINITY):

        if o != o:
            text = 'NaN'
        elif o == _inf:
            text = 'Infinity'
        elif o == _neginf:
            text = '-Infinity'
        else:
            # return _repr(o) # commented out
            return FloatRepr().repr(o) # changes made

        if not allow_nan:
            raise ValueError(
                "Out of range float values are not JSON compliant: " +
                repr(o))

        return text

이렇게 하면 .json float 값은 문자열이 되지 않습니다.

장점:

  • 모든 JSON 인코더 또는 Python의 repr과 연동됩니다.
  • 짧게(약간), 효과가 있는 것 같습니다.

단점:

  • 보기 흉한 regexp 해킹, 거의 테스트 안 했어.
  • 이차 복잡도.

    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
    

이 작업을 수행했습니다.:) 내 코드에는 항상 쉼표 뒤에 두 자리 숫자가 있습니다.

>>> 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)

당신의 프로젝트에서 몇 가지 테스트를 만드는 것을 잊지 마세요. 왜냐하면 나의 기능은 미래에 변경될 수 있는 python json 모듈 구현과 매우 관련이 있기 때문입니다.

언급URL : https://stackoverflow.com/questions/1447287/format-floats-with-standard-json-module

반응형