5

我和这个人有同样的问题,可能还有这个人,但我是来分享一些代码并回答问题的!

我在批处理作业中有一些代码,它通过 pyodbc 从 Microsoft Access 数据库中读取字段并准备输出以供显示。

这是一个片段。注意断言。

def format_currency(amount):
    if amount is None:
        return ""
    else:
        result = "$%.2f" % amount
        assert ":" not in result, (
            "That's weird. The value %r of class %s is represented as %s" %
             (amount, amount.__class__, result))
        return result

当我运行它时,它成功处理了 100,000 行,然后失败了:

AssertionError: That's weird. The value Decimal('54871.0000') of class <class
'decimal.Decimal'> is represented as $54870.:0

注意异常的冒号。它很少发生 - 大约在 300,000 条记录中出现一次。

当我尝试隔离它时,它当然有效。

from decimal import Decimal
print "$%.2f" % Decimal('54871.0000')

$54871.00

Access中的字段类型为:

  • 数据类型:货币
  • 小数位数:2
  • 输入掩码:
  • 默认值:
  • 验证规则:
  • 文本对齐:常规

基于证据不足,我模糊的指责怀疑:pyodbc 正在探索 Decimal 的内部,可能被 Access 损坏弄糊涂了。正如@ecatmur指出的那样

':' 在 ASCII 中是 '9' + 1

有人看到这个并解决了吗?

版本:

  • Python 2.7.4
  • pyodbc 3.0.6(最新)
  • 访问 2010
  • Windows 7的

进一步挖掘:

decimal模块是在 Python 中实现的。根据我的阅读,这些值由四个属性描述:_exp, _int, _sign,_is_special

怀疑有损坏,我打印出这些字段的值。

令人惊讶的是,对于故障版本和工作版本,我得到:

_exp: -4
_int: 548710000
_sign: 0
_is_special: False

这很奇怪。


decimal模块中,__float__函数的定义相当简单:

def __float__(self):
    """Float representation."""
    return float(str(self))

但是当我用坏数据做这个时:

print "Str", str(amount)
print "Float", float(amount)

我得到:

力量 54871.0000

浮动 54870.:

我学得越多,它就越不奇怪

4

1 回答 1

1

我能够重现该错误。我创建了一个访问表 [pyData] ...

ID - 自动编号
金额 - 货币(2 位小数)

...并用 50,000 到 60,000 之间的一百万行随机值填充它。当我运行我的测试脚本时,它在这里失败了

30815 : $50638.91
30816 : $52423.28
30817 :

Traceback (most recent call last):
  File "C:\__tmp\pyOdbcTest.py", line 20, in <module>
    print row.ID, ":", format_currency(row.Amount)
  File "C:\__tmp\pyOdbcTest.py", line 10, in format_currency
    (amount, amount.__class__, result))
AssertionError: That's weird. The value Decimal('58510.0000') of class <class 'decimal.Decimal'> is represented as $5850:.00

我还将该值 (58510.00) 和对您失败的值 (54871.00) 测试为具有相同结构的单独表中的单行,但它们都失败了。所以我们知道这不是早期 ODBC 调用中一些剩余的“垃圾”的功能。

考虑到它可能与数字末尾有“1”后跟零的数字有关,我尝试了 55871.00,但效果很好。53871.00 也工作得很好。将数字更改回 54871.00 会恢复错误。

我使用pypyodbc尝试了相同的测试并得到了相同的错误。我有点乐观,因为 pypyodbc 包含许多 Access 特定的功能,所以我认为它的一个用户可能以前遇到过这个问题,但显然没有。

最后,我将我的测试表升级为 SQL Server 2008 R2 Express,并使用 {SQL Server Native Client 10.0} 驱动程序尝试了相同的测试。从 Access(“货币”列类型)读取失败的数字在从 SQL Server 表(“金钱”列类型)读取时没有失败。

所以,目前我能提供的最好的“答案”是:

看起来它要么是:

  • pyodbc(和 pypyodbc,似乎与 pyodbc 密切相关)中的错误,或

  • Microsoft Access ODBC 驱动程序中的错误,或

  • 两者之间的“不幸的交互”(如果 ODBC 规范足够松散以至于两个组件在技术上都不是“错误的”)。

在任何情况下,看起来你都需要解决它,至少现在是这样。

编辑

由于我有这么多数字,我决定让脚本继续运行,看看还有哪些其他数字可能会被格式化为带有冒号。结果列表似乎都是整数(没有一分钱),所以我用 1 到 100,000 之间的整数进行了另一个测试。我在格式化字符串中发现了 260 个以冒号结尾的数字:

1451.0000 -> $1450.:0
1701.0000 -> $1700.:0
1821.0000 -> $1820.:0
1951.0000 -> $1950.:0
2091.0000 -> $2090.:0
...
98621.0000 -> $98620.:0
98710.0000 -> $9870:.00
99871.0000 -> $99870.:0

我在这里粘贴了整个列表。也许这可能会有所帮助。

编辑 - 问题已解决 (?)

我之前的测试是在 Python 2.7.3 版本下运行的。我刚刚将 Python 升级到 2.7.5 版(Win 32 位),pyodbc 仍然是 3.0.6 版,问题似乎已经消失了。

于 2013-05-18T13:09:55.273 回答