如何防止csv.DictWriter()或writerow()四舍五入我的浮点数?
我有一个字典,想把它写入一个csv文件,但字典里的浮点数在写入文件时被四舍五入了。我希望能保留最大的精度。
请问四舍五入是在哪里发生的,我该怎么防止它?
我做了什么
我参考了这里的DictWriter示例,并且我在Mac上运行的是Python 2.6.1(10.6 - 雪豹)。
# 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 >
3 个回答
这个方法可以用,但可能不是最好的或者最有效的方式:
>>> 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'
这是一个众所周知的bug^H^H^H特性。根据文档:
"""... 值为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)
生成的内容在准确性上没有变化。
看起来 csv 模块使用的是 float.__str__ 而不是 float.__repr__:
>>> print repr(1323494016.855676)
1323494016.855676
>>> print str(1323494016.855676)
1323494016.86
从 csv 的源代码 来看,这似乎是一个固定的行为。解决这个问题的方法是,在 csv 处理之前,把所有的浮点数值转换成它们的表示形式。可以使用类似这样的代码:d = dict((k, repr(v)) for k, v in d.items())
。
这里有一个详细的例子:
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