Python通过pyodbc在Access中以十进制数字插入冒号

Odd*_*ing 5 python ms-access pyodbc

我遇到了和这个家伙一样的问题,也可能是这个家伙,但我正在分享一些代码并回答问题!

我在批处理作业中有一些代码,它通过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
Run Code Online (Sandbox Code Playgroud)

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

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

注意异常的结肠.它很少发生 - 大约有300,000条记录中的一次.

当我试图隔离它时,它当然有效.

from decimal import Decimal
print "$%.2f" % Decimal('54871.0000')
Run Code Online (Sandbox Code Playgroud)

$ 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
Run Code Online (Sandbox Code Playgroud)

那真是怪了.


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

def __float__(self):
    """Float representation."""
    return float(str(self))
Run Code Online (Sandbox Code Playgroud)

但是,当我用坏数据执行此操作时:

print "Str", str(amount)
print "Float", float(amount)
Run Code Online (Sandbox Code Playgroud)

我明白了:

Str 54871.0000

Float 54870:

我越了解,越怪异它不会得到.

Gor*_*son 1

我能够重现该错误。我创建了一个 Access 表 [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
Run Code Online (Sandbox Code Playgroud)

我还测试了该值 (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
Run Code Online (Sandbox Code Playgroud)

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

编辑 - 问题已解决(?)

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