Python 行文件迭代与奇怪字符
我有一个很大的gzipped文本文件,需要逐行读取。我用的是下面的代码:
for i, line in enumerate(codecs.getreader('utf-8')(gzip.open('file.gz'))):
print i, line
在文件的某个后面部分,Python的输出和文件内容不一致。这是因为有些奇怪的特殊字符让Python误认为是换行符,导致行被拆分。当我用'vim'打开文件时,内容是正确的,但那些可疑的字符显示得很奇怪。我能做些什么来解决这个问题呢?
我尝试过其他编码方式,包括utf-16和latin-1。我也试过不使用任何编码。
我用'od'查看了文件,果然在不该有换行符的地方出现了\n字符。但是,这些“错误”的换行符前面有个奇怪的字符。我觉得这里可能有一些编码问题,有些字符是2个字节,但如果不正确查看,最后一个字节就是\n。
根据'od -h file'的结果,那个问题字符是'1d1c'。
如果我把:
gzip.open('file.gz')
替换成:
os.popen('zcat file.gz')
这样就能正常工作了(而且速度还快)。不过,我想知道我哪里出错了。
2 个回答
我在评论里问过:“给我们看看 print repr(weird_special_characters) 的输出。你在 vim 中打开文件时,什么是正确的?请比‘格式奇怪’更具体一些。”但是没有得到任何回复 :-(
你用 od
查看的是哪个文件?是 file.gz
吗?如果你在里面看到任何可以识别的内容,那就不是 gzip 文件!你看到的不是换行符,而是包含 0x0A 的二进制字节。
如果原始文件是 utf-8 编码的,那你为什么还要尝试其他编码呢?
“与 zcat 一起工作正常”是说你在没有 utf8 解码步骤的情况下得到了可识别的数据吗?
我建议你简化你的代码,一步一步来……比如看看这个问题的接受答案:这个问题。再试一次,并请展示你运行的确切代码,描述结果时使用 repr()。
更新 看起来 DS 猜到了你想解释的关于 \x1c 和 \x1d 的内容。
这里有一些关于为什么会这样发生的说明:
在 ASCII 中,只有 \r 和 \n 被视为换行符:
>>> import pprint
>>> text = ''.join('A' + chr(i) for i in range(32)) + 'BBB'
>>> print repr(text)
'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\nA\x0bA\x0cA\rA\x0eA\x0fA\x10
A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x1dA\x1eA\x1fBBB'
>>> pprint.pprint(text.splitlines(True))
['A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\n', # line break
'A\x0bA\x0cA\r', # line break
'A\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x
1dA\x1eA\x1fBBB']
>>>
但是在 Unicode 中,字符 \x1D (文件分隔符)、\x1E (组分隔符) 和 \x1E (记录分隔符) 也可以作为行结束符:
>>> text = u''.join('A' + unichr(i) for i in range(32)) + u'BBB'
>>> print repr(text)
u'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\nA\x0bA\x0cA\rA\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x1dA\x1eA\x1fBBB'
>>> pprint.pprint(text.splitlines(True))
[u'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\n', # line break
u'A\x0bA\x0cA\r', # line break
u'A\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1c', # line break
u'A\x1d', # line break
u'A\x1e', # line break
u'A\x1fBBB']
>>>
无论你使用什么编码,这种情况都会发生。你仍然需要弄清楚你需要使用什么(如果有的话)编码。你还需要确定原始文件是否真的一个文本文件,而不是一个二进制文件。如果是文本文件,你需要考虑文件中 \x1c 和 \x1d 的含义。
再试一次,不要使用编解码器。下面的内容展示了当使用编解码器时会出现的问题,而不使用它时就不会有这个问题:
import gzip
import os
import codecs
data = gzip.open("file.gz", "wb")
data.write('foo\x1d\x1cbar\nbaz')
data.close()
print list(codecs.getreader('utf-8')(gzip.open('file.gz')))
print list(os.popen('zcat file.gz'))
print list(gzip.open('file.gz'))
输出结果:
[u'foo\x1d', u'\x1c', u'bar\n', u'baz']
['foo\x1d\x1cbar\n', 'baz']
['foo\x1d\x1cbar\n', 'baz']