如何防止csv.DictWriter()或writerow()舍入我的花车?

时间:2011-12-10 08:02:50

标签: python file-io csv floating-point rounding

我有一个我要写入csv文件的字典,但是当我将它们写入文件时,字典中的浮点数会四舍五入。我想保持最高精度。

四舍五入发生在哪里,如何防止它?

我做了什么

我关注了DictWriter example here,我在Mac上运行Python 2.6.1(10.6 - Snow Leopard)。


# 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

&LT; EOF&gt;

3 个答案:

答案 0 :(得分:4)

看起来 csv 正在使用 float .__ str __ 而不是 float .__ repr __

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

查看csv source,这似乎是一种硬连线行为。解决方法是在csv到达之前将所有浮点值强制转换为其repr。使用类似: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

答案 1 :(得分:2)

这是一个已知的错误^ H ^ H ^ Hfeature。根据{{​​3}}:

“”“...值None被写为空字符串。[snip]所有其他非字符串数据在写入之前用str()进行字符串化。”“”

不要依赖默认转化。使用repr()表示浮点数。 unicode个对象需要特殊处理;见手册。检查文件的使用者是否接受x in的datetime.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)产生的准确度没有变化。

答案 2 :(得分: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'