如何避免csv.DictWriter()或writerow()将我的浮点数四舍五入?

12
我有一个字典想要写入csv文件,但是当我把字典写入文件时,其中的浮点数被四舍五入了。我想保留最大精度。
在哪里进行了四舍五入,我该如何防止它发生?

我的做法

我遵循了这里的DictWriter示例,并且我正在Mac(10.6 - Snow Leopard)上运行Python 2.6.1。
# my import statements
import sys
import csv

这是我的字典(d)包含的内容:

>>> d = runtime.__dict__
>>> d
{'time_final': 1323494016.8556759,
'time_init': 1323493818.0042379,
'time_lapsed': 198.85143804550171}

这些值确实是浮点数:

>>> type(runtime.time_init)
<type 'float'>

然后我设置了我的编写器并编写了标题和值:

f = open(log_filename,'w')
fieldnames = ('time_init', 'time_final', 'time_lapsed')
myWriter = csv.DictWriter(f, fieldnames=fieldnames)
headers = dict( (n,n) for n in fieldnames )
myWriter.writerow(headers)
myWriter.writerow(d)
f.close()

但是当我查看输出文件时,我得到的是四舍五入的数字(即浮点数):

time_init,time_final,time_lapsed
1323493818.0,1323494016.86,198.851438046

< EOF >


1
不是你的问题,但在Python 2.x中,始终以二进制模式('rb'或'wb')打开csv文件。 - John Machin
感谢您的提醒和审查我的问题。+1 - aDroid
3个回答

7

看起来 csv 正在使用 float.__str__ 而不是 float.__repr__

>>> print repr(1323494016.855676)
1323494016.855676
>>> print str(1323494016.855676)
1323494016.86

看起来这是一种硬编码的行为,可以采用将所有浮点值转换为其repr的方法来解决。例如: d = dict((k, repr(v)) for k, v in d.items())。请参考csv source
以下是一个实际示例:
import sys, csv

d = {'time_final': 1323494016.8556759,
     'time_init': 1323493818.0042379,
     'time_lapsed': 198.85143804550171
}

d = dict((k, repr(v)) for k, v in d.items())

fieldnames = ('time_init', 'time_final', 'time_lapsed')
myWriter = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
headers = dict( (n,n) for n in fieldnames )
myWriter.writerow(headers)
myWriter.writerow(d)

这段代码会产生以下输出:
time_init,time_final,time_lapsed
1323493818.0042379,1323494016.8556759,198.85143804550171

更精细的方法将只替换浮点数:

d = dict((k, (repr(v) if isinstance(v, float) else str(v))) for k, v in d.items())

注意,我刚刚为Py2.7.3修复了这个问题,所以将来不应该成为问题。请参见http://hg.python.org/cpython/rev/bf7329190ca6

太棒了,完美运行!另外,感谢提供源链接。我还在学习如何浏览Python文档,对我来说有点笨拙。我也将datetime添加到字典中,并且它被写成“datetime.date(2011,12,10)”,这是你提供的预期结果。我只需将日期放入文件名中并以此方式获取即可。做得好!+1 - aDroid
一个令人惊叹的大锤,虽然效果不完美:对浮点数进行“修复”,搞砸了日期时间。 - John Machin
是的,但我在原始问题中没有指定日期时间,所以这不是原始答案需要考虑的事情。 - aDroid
2
太棒了。我不知道这里的问题有多少直接对源代码做出贡献,但对于我的第一个问题,我很高兴发表了它!在过去几周中,Python一直在我身边成长,现在我的更改(即您代表我所做的更改)已经被合并到源代码中,我现在可以说我已经完全被Python同化了。 :) 再次感谢。 - aDroid

2

这是一个已知的bug^H^H^Hfeature。根据文档

"""... 值为None时,写入为空字符串。[省略] 所有其他非字符串数据在写入之前都会被字符串化为str()。"""

不要依赖默认转换。对于浮点数,请使用repr()unicode对象需要特殊处理,请参见手册。检查文件的使用者是否接受datetime.x对象的默认格式,其中x为(datetime、date、time、timedelta)。

更新:

对于浮点对象,"%f" % value不是repr(value)的良好替代品。标准是文件的使用者是否能够重现原始浮点对象。repr(value)可以保证这一点,而"%f" % value则不能。

# Python 2.6.6
>>> nums = [1323494016.855676, 1323493818.004238, 198.8514380455017, 1.0 / 3]
>>> for v in nums:
...     rv = repr(v)
...     fv = "%f" % v
...     sv = str(v)
...     print rv, float(rv) == v, fv, float(fv) == v, sv, float(sv) == v
...
1323494016.8556759 True 1323494016.855676 True 1323494016.86 False
1323493818.0042379 True 1323493818.004238 True 1323493818.0 False
198.85143804550171 True 198.851438 False 198.851438046 False
0.33333333333333331 True 0.333333 False 0.333333333333 False

注意,在上面的例子中,通过检查生成的字符串,似乎没有一个%f的情况起作用。在2.7之前,Python的repr总是使用17个有效数字。在2.7中,这被改为使用最少的数字,仍然保证float(repr(v)) == v。这个差别不是一个舍入误差。

# Python 2.7 output
1323494016.855676 True 1323494016.855676 True 1323494016.86 False
1323493818.004238 True 1323493818.004238 True 1323493818.0 False
198.8514380455017 True 198.851438 False 198.851438046 False
0.3333333333333333 True 0.333333 False 0.333333333333 False

请注意,上面第一列中的repr()结果有所改善。
根据评论的更新2:“谢谢您提供有关Python 2.7的信息。不幸的是,我只能使用2.6.2(运行在无法升级的目标机器上)。但我会记住这个问题,以备将来需要使用脚本。”
这并不重要。float('0.3333333333333333') == float('0.33333333333333331')在所有版本的Python上都返回 True 。这意味着你可以在2.7上编写文件,并且它在2.6上读取相同,反之亦然。在repr(a_float_object)产生的结果的准确性上没有变化。

感谢指出为什么会发生这种情况。我可能已经看到了“使用str()进行字符串化”,但是我的Python新手水平并没有意识到str()的问题。 - aDroid
OP表示他是Python的新手。我们需要的是能够解决他问题的可行代码,而不是轻率的学术回答。 - Raymond Hettinger
OP的问题中的代码表明他“新手Python”的说法很谦虚;他似乎能够编写迭代字典并更新其值的代码,而无需手把手地指导。 - John Machin
虽然我只能在搜索和调整了几个小时后才做到这一点,但最终我成功了。直到我遇到了原始问题,4个小时的抓狂也没有解决。你们两个的回答都对不同的观点有帮助。感谢关于Python 2.7的信息。不幸的是,我受限于2.6.2(运行在目标机器上,无法升级)。但我会记住这个,为未来的脚本做准备。 - aDroid

1

这个方法可以工作,但可能不是最好/最有效的方法:

>>> f = StringIO()
>>> w = csv.DictWriter(f,fieldnames=headers)
>>> w.writerow(dict((k,"%f"%d[k]) for k in d.keys()))
>>> f.getvalue()
'1323493818.004238,1323494016.855676,198.851438\r\n'

看起来你的浮点数也被四舍五入了,除非这是getvalue()的副作用。我会调查一下。 - aDroid
与getvalue无关。在某些情况下,%f格式化仅使用6位小数。 “看起来像”是具有欺骗性的;请参阅我的更新答案。 - John Machin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接