Skip to main content
 首页 » 编程设计

Python decimal.Decimal 不一样

2025年05月04日13grandyang

我在用 Python 编码为 JSON 时遇到问题,特别是 decimal.Decimal 值。我正在使用它为 Google App Engine 应用程序输出 JSON。

为了避免 Python 中默认的 json 模块告诉我它不能处理 decimal.Decimal 对象的异常,我使用了这个编码器子类:

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

在其他应用程序上,这确实有效。在这种情况下,它没有。在经历了很多挫折之后,我发现这会产生奇怪的结果:

print id(decimal.Decimal) 
print id(type(o)) 

人们会期望 id 是相同的,因为同一个类对象在内存中只驻留一次是有意义的。因为 id 不同,isinstance() 不起作用。

会不会是 decimal.Decimal 已经导入到其他地方,例如在 App Engine 和/或 webapp2 包中?

以下模块在我的系统(OSx 10.10、Python 2.7.6、GAE SDK 1.9.20)上重现了错误。只需创建一个 GAE 应用程序并将其放入 main.py 中:

import webapp2, decimal, json, MySQLdb, sys 
 
class DecimalEncoder(json.JSONEncoder): 
    def default(self, o): 
        print id(decimal.Decimal) 
        print id(type(o)) 
        if isinstance(o, decimal.Decimal): 
            return float(o) 
        return super(DecimalEncoder, self).default(o) 
 
class MainHandler(webapp2.RequestHandler): 
    def get(self): 
        db = MySQLdb.connect(unix_socket='/var/mysql/mysql.sock', host='localhost', user='root', db='ssss', charset='utf8') 
        cursor = db.cursor(MySQLdb.cursors.DictCursor) 
        cursor.execute("SELECT id, price FROM product WHERE id = 1") 
        record = cursor.fetchone() 
 
        self.response.headers['Content-Type'] = 'application/json' 
        self.response.write(json.dumps( 
            record, 
            cls=DecimalEncoder, 
            indent=4, 
            separators=(',', ': ') 
        )) 
 
app = webapp2.WSGIApplication([ 
    ('/', MainHandler) 
], debug=True) 

数据库表:

CREATE TABLE `product` ( 
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
  `price` decimal(10,2) unsigned DEFAULT NULL, 
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; 
INSERT INTO product VALUES(0, 5.00); 

请您参考如下方法:

看来 decimal.Decimal 类在 Google App Engine SDK 中某处打了补丁(或者重新加载了模块),这是在 MySQL 转换之间完成的库导入 decimal 和你导入相同的。

幸运的是,我们可以通过更新 MySQL 转换表来解决这个问题:

from MySQLdb.constants import FIELD_TYPE 
from MySQLdb.converters import conversions 
import decimal 
 
conversions[FIELD_TYPE.DECIMAL] = conversions[FIELD_TYPE.NEWDECIMAL] = decimal.Decimal 

就是这样;上面的代码重置了 MySQL 类,您的 JSON 编码器类型检查成功。

另一种方法是查找 MySQLdb 正在使用的类:

class DecimalEncoder(json.JSONEncoder): 
    def default(self, o): 
        if isinstance(o, MySQLdb.converters.conversions[MySQLdb.constants.FIELD_TYPE.DECIMAL]): 
            return float(o) 
        return super(DecimalEncoder, self).default(o)