使用Python的boto库尝试插入浮点数时出现DynamoDBNumberError

16

代码片段:

conn = dynamo_connect()

company = Table("companydb",connection=conn)

companyrecord = {'company-slug':'www-google-com12','founding-year':1991, 'randomlist' :[1,2,3,4,5], 'randomdict' : {'a':[1,2,3],'b':'something','randomnumber':10.55} }

company.put_item(data=companyrecord)

我遇到了以下错误:

File "C:\Python27\lib\site-packages\boto\dynamodb2\items.py", line 329, in prepare_full
    final_data[key] = self._dynamizer.encode(value)
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 279, in encode
    return {dynamodb_type: encoder(attr)}
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 335, in _encode_m
    return dict([(k, self.encode(v)) for k, v in attr.items()])
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 279, in encode
    return {dynamodb_type: encoder(attr)}
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 305, in _encode_n
    raise DynamoDBNumberError(msg)
boto.dynamodb.exceptions.DynamoDBNumberError: BotoClientError: Inexact numeric for `10.55`

能否包含实际导致错误的代码? - tzaman
@tzaman 我已经更新了我上面的帖子中的代码。我尝试插入一个没有浮点值的字典记录,插入工作正常。即使将10.55作为'10.55'放入,它也可以工作,但是Dynamodb数据类型被解释为字符串而不是数字。 - Rohan S
7个回答

11

如果您正在处理较大的数据集,并且希望避免逐条记录进行十进制转换,则此方法将有所帮助。

from decimal import Decimal

import json

changed_data = json.loads(json.dumps(data), parse_float=Decimal)

1
json包顺便提一句,不处理集合。 - Matt Moehr

6

是的,GitHub上已知有与浮点数相关的问题。可能有两种解决方法,第一种是如果您愿意存储10.5而不是10.55,那么我想它会正常工作。另一种方法是将浮点值存储为字符串或整数,稍后在访问时进行调整。

如果您选择字符串部分,那么可以将其存储为'10.55'而不是10.55,然后当您从表中访问值时,只需使用float("10.55")即可。

另一种方法是将其存储为整数,首先选择精度值(例如2位小数),然后将10.55乘以100存储为1055,在访问时,您只需使用1055/100.0即可得到10.55


3
这不是问题的解决方案,只是一个小技巧。 - ZdaR
我不想失去任何精度数据,所以只能选择第二个选项 - 将Python字典中的所有浮点值转换为字符串,并在调用put-item之前重新构造字典,以便清除boto验证并避免抛出异常。另外,需要注意的一点是字典可以嵌套,因此必须在任何情况下都能正常工作。您是否知道有任何库可以执行此功能? - Rohan S
没错,所以尝试使用string代替float - ZdaR
很有道理,我编写了自己的包装器将所有浮点出现转换为字符串值。感谢@anmol_uppal的帮助。 - Rohan S

5

4

以下是我通过重写typeSerializer来插入浮点数的解决方案。

from boto3.dynamodb import types
from decimal import Decimal, localcontext
import re

class CustomSerializer(types.TypeSerializer):

    def _is_number(self, value):
        if isinstance(value, (int, Decimal, float)):
            return True
      return False

# Add float-specific serialization code
def _serialize_n(self, value):
    if isinstance(value, float):
        with localcontext(types.DYNAMODB_CONTEXT) as context:
            stringify_val = str(value)
            number = str(context.create_decimal(stringify_val))
            return number

    number = super(CustomSerializer, self)._serialize_n(value)
    return number

serializer = CustomSerializer()
serialized_data = serializer.serialize(data)

2
Python3提供float.hex() / .fromhex()来将浮点数以字符串形式存储而不会失去精度:

两种方法支持十六进制字符串的转换。由于Python的浮点数在内部是以二进制数存储的,因此将浮点数转换为或从十进制字符串转换通常涉及到微小的舍入误差。相比之下,十六进制字符串允许精确的表示和指定浮点数。这在调试和数字处理中可能非常有用。

如果您不想失去任何精度,这可能是一种替代@ZdaR的解决方案,该解决方案使用str()float()进行转换。
[注意:我是一个新用户,无法评论ZdaR's solution]

1

你需要将浮点数值解析为Decimal类型

from datetime import datetime
from decimal import Decimal

decimal_value = Decimal(datetime.utcnow().timestamp())

1

希望这对一些人有用。

我的结构如下:

{
    "hash_key": 1,
    "range_key": 2,
    "values": {
        "valueA": 1.2,
        "valueB": 2.1,
        "valueC": 3.1,
        "valueD": 4.1,
        "valueE": 5.1,
        "valueF": 6.1
    }
}

我有一个叫做parameters的对象中存储了这个词典。 额外说明:我进行了这个验证,因为并不总是收到values键的值:

values = parameters['values'] if 'values' in parameters else {}

if values: # So... this made the trick:
    parameters['values'] = [{key: str(value)} for key, value in values.items()]

这种转换使得工作变得更加顺利。

此外,我正在使用

boto3.resource('dynamodb')
table = dynamo.Table(TABLE_NAME)
table.put_item(Item=parameters, ReturnValues='ALL_OLD')

完整的函数如下所示:
def create_item(parameters):
    dynamo = boto3.resource('dynamodb')
    table = dynamo.Table('your_table_name')

    parameters['hash_key'] = str(parameters['hash_key'])
    parameters['range_key'] = str(parameters['range_key'])
    values = parameters['values'] if 'values' in parameters else {}

    if values:
        parameters['values'] = [{key: str(value)} for key, value in values.items()]

    try:
        response = table.put_item(Item=parameters, ReturnValues='ALL_OLD')
    except ClientError as e:
        response = e.response['Error']
        # this is a local function to give format to the error
        return create_response(response['Message'], response)
    return response

不确定这是否是一个tldr案例,希望它能有所帮助c:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接