python和JSON库中的完全精度数字

时间:2017-09-05 23:15:54

标签: python json floating-point precision

以下代码遇到一个小问题,并带有递归错误:

100s错误打印结束于此:

RuntimeError: maximum recursion depth exceeded while calling a Python object

如下所示,我的代码不是递归的,因此DecimalEncoder会发生一些事情。

代码

import json
import decimal      # tell json to leave my float values alone

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return decimal.Decimal(o)
            #return str(o)
        return super(DecimalEncoder, self).default(o)

class JSONUtils:
    def __init__( self, response ):
        self.response = response
        self.jsonData = None
        self.LoadData( )

        print 'jsonData: ' + json.dumps( self.jsonData, cls=DecimalEncoder, indent=2 )

    def LoadData ( self ):
        if ( self.jsonData == None ):
            if ( type( self.response ) == str or type( self.response ) == unicode ):
                #self.jsonData = json.loads(self.response )
                self.jsonData = json.loads(self.response, parse_float=decimal.Decimal )

    def GetJSONChunk( self, path ):
        returnValue = ''
        curPath     = ''
        try:
            if ( type( path ) == str ):
                returnValue = self.jsonData[path]
            elif (type( path ) == list):
                temp = ''
                firstTime = True
                for curPath in path:
                    if firstTime == True:
                        temp = self.jsonData[curPath]
                        firstTime = False
                    else:
                        temp = temp[curPath]
                returnValue = temp
            else:
                print 'Unknown type in GetJSONChunk: ' + unicode( type( path ))
        except KeyError as err:
            ti.DBG_OUT( 'JSON chunk doesn\'t have value: ' + unicode( path ))
            returnValue = self.kNoNode
        except IndexError as err:
            ti.DBG_OUT( 'Index does not exist: ' + unicode( curPath ))
            returnValue = self.kInvalidIndex

        return returnValue

myJSON = JSONUtils( unicode('{ "fldName":4.9497474683058327445566778899001122334455667788990011 }' ))
value =  str( myJSON.GetJSONChunk ( 'fldName' ))
print str( type( value ))
print value

如果我为字符串交换返回decimal.Decimal(0)。它消除了错误但是你可以看到的值,以字符串形式返回。

#return decimal.Decimal(o)
return str(o)

这个输出很接近,但我需要一个类型的双倍:

jsonData: {
  "fldName": "4.9497474683058327445566778899001122334455667788990011"
}
<type 'str'>
4.9497474683058327445566778899001122334455667788990011

如果我交换这些线条,您可以看到原始问题,这会导致精确度下降。

#self.jsonData = json.loads(self.response )
self.jsonData = json.loads(self.response, parse_float=decimal.Decimal )

1 个答案:

答案 0 :(得分:1)

您遇到此递归错误,因为default中的DecimalEncoder方法返回的是它应该处理的相同类型,而不是将其转换为可序列化类型。

如果您在调试器中调试代码,您将能够在给定时刻看到JSONEncoder的内部调用正在尝试将值转换为默认的可序列化类型之一,并且没有人匹配,它会调用后备,这是default类中的DecimalEncoder方法。

这就是声明return str(o)有效的原因。它返回的是与Decimal不同的类型,这是一个可序列化的类型。

我建议您按照user2357112的建议,考虑将Decimal编码为字符串,并将其转换回另一端。

https://stackoverflow.com/a/38357877/7254201的答案指出了在JSON中使用字符串而不是Decimal的一些原因。

更新#1

下面我的建议是关于如何将小数转换为字符串,以便稍后可以获得原始值。

使用DecimalEncoder将新的dict中的小数值设为有一个名为value的密钥,另一个名为type,因此您将两者都设为字符串。当然,在反序列化值时会有一些进一步的工作。

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return {
                'value': str(o),
                'type': str(type(o).__name__) # you can use another code to provide the type.
            }
            #return str(o)
        return super(DecimalEncoder, self).default(o)

遵循此策略,您的代码输出如下所示:

jsonData: {
  "fldName": {
    "type": "decimal", 
    "value": "4.9497474683058327445566778899001122334455667788990011"
  }
}

正如我所看到的,这个问题就是在序列化时保持值及其类型。在这些情况下,我尝试在可能的情况下将数据及其“元”信息分开。

更新#2

如何解码

现在,您的对象如下:

  "fldName": {
    "type": "decimal", 
    "value": "4.9497474683058327445566778899001122334455667788990011"
  }

您可能希望将其反序列化为:

  {
    "fldName": Decimal('4.9497474683058327445566778899001122334455667788990011')
  }

因此,您可以根据此要点代码执行某些操作:https://gist.github.com/simonw/7000493

我将gist代码稍微更改为以下内容:

class DecimalDecoder(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, obj):
        if 'type' not in obj:
            return obj

        obj_type = obj['type']
        if obj_type == 'decimal':
            return decimal.Decimal(obj.get('value'))
        return obj

我的测试代码是:

    jsonData = json.dumps( self.jsonData, cls=DecimalEncoder, indent=2 )
    print 'jsonData: ' + jsonData
    newObj = json.loads(jsonData, cls=DecimalDecoder)
    print 'newObj: ' + str(newObj)

输出结果为:

jsonData: {
  "fldName": {
    "type": "decimal", 
    "value": "4.9497474683058327445566778899001122334455667788990011"
  }
}
newObj: {u'fldName': Decimal('4.9497474683058327445566778899001122334455667788990011')}