#python #json #google-app-engine #webapp2
#python #json #google-app-engine #webapp2
Вопрос:
В настоящее время я работаю с Python и создаю простой API в Google App Engine, который позволяет пользователю использовать глаголы GET, POST, DELETE и PUT, выполняя вызовы API с помощью командной строки. Я использую curl для обработки данных с помощью вызовов API. Мне удалось успешно добавить данные и отобразить их обратно, но проблема в том, что у меня есть атрибут для ‘price’, и эта цена хранится в хранилище данных GAE (Google App Engine) в виде числа с плавающей запятой, а число форматируется по мере отправки, но когда JSON показывает, что находится в хранилище данных, оно имеет другой формат. Например, при сохранении цены 8,99, ответ JSON показывает 8,9900000000000002. Я почти уверен, что это не проблема GAE, а проблема JSON.
Пример проблемы:
curl --data "name=t-shirtamp;description=Star Trekamp;price=8.99amp;user=test1" -H "Accept: application/json" https://mywebsite.appspot.com/product
Правильно сохраняется в хранилище данных, но возвращает JSON следующим образом:
{"description": "Star Trek", "price": 8.9900000000000002, "name": "t-shirt", "user": "test1", "key": 5206065687822336}
Вот мои models.py
from google.appengine.ext import ndb
class Model(ndb.Model):
def to_dict(self):
d = super(Model, self).to_dict()
d['key'] = self.key.id()
return d
class User(Model):
username = ndb.StringProperty(required=True)
password = ndb.StringProperty(required=True)
class Product(Model):
name = ndb.StringProperty(required=True)
description = ndb.StringProperty(required=True)
price = ndb.FloatProperty()
color = ndb.StringProperty()
size = ndb.StringProperty()
user = ndb.StringProperty(required=True)
class Sales(Model):
products = ndb.KeyProperty(repeated=True)
datetime = ndb.DateTimeProperty(required=True)
quantity = ndb.IntegerProperty(repeated=True)
product_cost = ndb.FloatProperty(repeated=True)
total_cost = ndb.FloatProperty()
latitude = ndb.FloatProperty()
longitude = ndb.FloatProperty()
user = ndb.KeyProperty(required=True)
def to_dict(self):
d = super(Sales, self).to_dict()
d['products'] = [i.id() for i in d['products']]
return d
Вот product.py:
import webapp2
from google.appengine.ext import ndb
import models
import json
class Product(webapp2.RequestHandler):
#Create a Product entity
def post(self):
if 'application/json' not in self.request.accept:
self.response.status = 406
self.response.status_message = 'Not acceptable, API only supports application/json MIME type.'
return
new_product = models.Product()
name = self.request.get('name', default_value=None)
description = self.request.get('description', default_value=None)
price = self.request.get('price', default_value=0)
color = self.request.get('color', default_value=None)
size = self.request.get('size', default_value=None)
user = self.request.get('user', default_value=None)
if name:
new_product.name = name
else:
self.response.status = 400
self.response.status_message = 'Invalid request, name required'
if description:
new_product.description = description
else:
self.response.status = 400
self.response.status_message = 'Invalid request, description required'
if price:
new_product.price = float(price)
if color:
new_product.color = color
if size:
new_product.size = size
if user:
new_product.user = user
else:
self.response.status = 400
self.response.status_message = 'Invalid request, username required'
key = new_product.put()
out = new_product.to_dict()
self.response.write(json.dumps(out))
return
#Return an Product entity
def get(self, **kwargs):
if 'application/json' not in self.request.accept:
self.response.status = 406
self.response.status_message = 'Not acceptable, API only supports application/json MIME type.'
self.response.write(self.response.status_message)
return
#Return selected product details
if 'id' in kwargs:
out = ndb.Key(models.Product, int(kwargs['id'])).get().to_dict()
self.response.write(json.dumps(out))
#Return all product ids
else:
q = models.Product.query()
keys = q.fetch(keys_only=False)
results = {x.key.id() : x.to_dict() for x in keys}
self.response.write(json.dumps(results))
Я прошу прощения за излишество, но я хотел убедиться, что все доступно для просмотра. Любая помощь была бы высоко оценена. Я просмотрел некоторые похожие элементы, но из-за того, как я использую словарь здесь, я, похоже, не могу правильно использовать этот формат.
Заранее благодарю вас!
Ответ №1:
Я не думаю, что это на самом деле проблема. Если вы хотите понять, почему существует это несоответствие, вы можете прочитать статью Википедии о числах с плавающей запятой. Проблема с TLDR заключается в том, что уже существует бесконечно много рациональных чисел от 0 до 1, но компьютер способен хранить только конечный объем данных. Более того, вы обычно хотите хранить действительные числа в пределах 32 (одинарных) или 64 (двойных) бит данных, чтобы обеспечить эффективное вычисление. Таким образом, номера компьютеров являются лишь подмножеством действительных чисел, и вам часто приходится округлять до ближайшего номера компьютера. Это не было бы проблемой, если бы точка всегда находилась в одном и том же месте, и вы использовали базовые числа 10, чтобы вы знали, что нет ошибки при округлении «8,99», но это не относится к обычным числам с плавающей запятой, как определено в IEEE 754.
В основном здесь выполняется 8.99 = 8.9900000000000002.
У вас есть несколько вариантов устранения проблемы:
1) Не устраняйте проблему и просто округляйте отображаемые значения
Предупреждение: Скорее всего, это не то, что вы хотите.
Обычно можно просто округлить ошибку при отображении, поскольку обычно ошибка очень маленькая, например, в игре это, вероятно, было бы приемлемо. Однако в этом случае вы, похоже, создаете магазин, и это может быть проблематично, если в разных местах есть разные округления. Например, при определенных обстоятельствах это может привести к разнице в 0,01 для заказа, что может привести к различного рода проблемам. Не храните цены / суммы денег в числах с плавающей запятой.
2) Используйте целочисленные значения [вероятно, лучший вариант здесь]
Вместо сохранения 8,99 вы сохраняете 899 как целое число в базе данных. В целочисленной арифметике нет проблем с округлением, если вы используете только сложение / вычитание.
Вероятно, это немного неудобно, поскольку потребует обновления базы данных и отображения чисел (т. Е. вам придется вставить точку в нужном месте). Но, вероятно, это самый безопасный вариант, который у вас есть, и который обычно считается хорошим вариантом, если у вас нет доступа к специализированным номерам.
3) Используйте числа с фиксированной точкой с правильным основанием
Если вы выполняете много арифметических операций с такими числами, возможно, было бы удобнее использовать библиотеку чисел с фиксированной запятой и в основном использовать специально разработанное представление рациональных чисел для вашей проблемы. Это в основном то же самое, что и вариант 2, но вы не реализуете все самостоятельно и обладаете большей гибкостью. Однако я лично выбрал бы вариант 2, поскольку я не знаю, существует ли такая библиотека, которую вы могли бы использовать как в Python, так и в Javascript, а также это может просто включать необязательную зависимость от проекта.