StringIO和csv模块中通用换行模式的意外行为
考虑以下内容(在Windows下的Python 3.2):
>>> import io
>>> import csv
>>> output = io.StringIO() # default parameter newline=None
>>> csvdata = [1, 'a', 'Whoa!\nNewlines!']
>>> writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC)
>>> writer.writerow(csvdata)
25
>>> output.getvalue()
'1,"a","Whoa!\nNewlines!"\r\n'
为什么这里只有一个\n
- 难道它不应该在启用了通用换行模式的情况下被转换成\r\n
吗?
当这个模式开启时,在输入时,行结束符
\n
、\r
或\r\n
都会被转换成\n
,然后再返回给调用者。相反,在输出时,\n
会被转换成系统默认的行分隔符os.linesep
。
2 个回答
来自StringIO的文档:
换行符的参数和TextIOWrapper的用法一样。默认情况下,不进行换行符的转换。
所以,StringIO通常不会进行换行符的转换。这个默认设置是合理的,因为StringIO并不是在写入硬盘,所以它不需要转换成特定平台的换行符。
正如约翰所指出的,csv模块会处理自己的通用换行符,但这只针对行结束,而不是字符串内部的换行符。
在第三个字段里出现的单个 \n
是作为数据字符存在的。因此,这个字段被加上了引号,这样 CSV 读取器就会把它当作数据的一部分来处理。它并不是“行结束符”(应该叫做行分隔符)或其一部分。为了更好地理解引号的作用,可以去掉 quoting=csv.QUOTE_NONNUMERIC
。
\r\n
是因为 CSV 用 dialect.lineterminator
来结束行,默认值就是 \r\n
。换句话说,“通用换行符”的设置被忽略了。
更新
关于 io.StringIO
的 2.7 和 3.2 文档在 newline 参数方面几乎是一样的。
newline 参数的工作方式和 TextIOWrapper 的一样。默认情况下不进行换行符转换。
我们来看下面的第一句话。第二句话对于输出是正确的,这取决于你对“默认”和“换行符转换”的理解。
TextIOWrapper 文档:
newline 可以是 None, '', '\n', '\r' 或 '\r\n'。它控制行结束符的处理。如果是 None,就启用了通用换行符。在输入时,行结束符 '\n'、'\r' 或 '\r\n' 会被转换为 '\n',然后返回给调用者。相反,在输出时,'\n' 会被转换为系统默认的行分隔符 os.linesep。如果 newline 是其他合法值,那么在读取文件时,这个换行符就会被当作换行符返回,并且不会被转换。在输出时,'\n' 会被转换为指定的换行符。
在 Windows 上的 Python 3.2:
>>> from io import StringIO as S
>>> import os
>>> print(repr(os.linesep))
'\r\n'
>>> ss = [S()] + [S(newline=nl) for nl in (None, '', '\n', '\r', '\r\n')]
>>> for x, s in enumerate(ss):
... m = s.write('foo\nbar\rzot\r\n')
... v = s.getvalue()
... print(x, m, len(v), repr(v))
...
0 13 13 'foo\nbar\rzot\r\n'
1 13 12 'foo\nbar\nzot\n'
2 13 13 'foo\nbar\rzot\r\n'
3 13 13 'foo\nbar\rzot\r\n'
4 13 13 'foo\rbar\rzot\r\r'
5 13 15 'foo\r\nbar\rzot\r\r\n'
>>>
第 0 行显示,如果没有 newline
参数,得到的“默认值”是不会对 \n
(或其他字符)进行转换的。它绝对不是把 '\n'
转换为 os.linesep
第 1 行显示,使用 newline=None
(应该和第 0 行一样,对吧??)实际上是 输入 的通用换行符转换——真奇怪!
第 2 行:newline=''
没有变化,和第 0 行一样。它绝对不是把 '\n'
转换为 ''
。
第 3、4 和 5 行:正如文档所说,'\n'
会被转换为 newline
参数的值。
更新 2 为了与内置的 open()
一致,默认值应该是 os.linesep
,如文档所述。要实现输出时不进行转换的行为,可以使用 newline=''
。注意:open()
的文档更清晰。我明天会提交一个错误报告。