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中字段的类型是:
基于证据不足,我模糊的指责怀疑:pyodbc正在戳与Decimal的内部结构,可能被Access腐败所迷惑.正如@ecatmur 指出:
ASCII中的':'是'9'+ 1
有人见过这个并解决了吗?
版本:
进一步挖掘:
该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:
我越了解,越怪异它不会得到.
我能够重现该错误。我创建了一个 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,问题似乎已经消失。