使用seek()和read()时出现UnicodeDecodeError
我正在跟着《Programming Python》里的示例代码学习,但有些地方让我感到困惑。下面是一个将简单字符串写入文件然后再读取的代码:
>>> data = 'sp\xe4m' # data to your script
>>> data, len(data) # 4 unicode chars, 1 nonascii
('späm', 4)
>>> data.encode('utf8'), len(data.encode('utf8')) # bytes written to file
(b'sp\xc3\xa4m', 5)
>>> f = open('test', mode='w+', encoding='utf8') # use text mode, encoded
>>> f.write(data)
>>> f.flush()
>>> f.seek(0); f.read(1) # ascii bytes work
's'
>>> f.seek(2); f.read(1) # as does 2-byte nonascii
'ä'
>>> data[3] # but offset 3 is not 'm' !
'm'
>>> f.seek(3); f.read(1)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xa4 in position 0:
unexpected code byte
让我困惑的是,为什么当数据字符串是utf8编码时,还会出现这个UnicodeDecodeError错误?用手动的f.read()读取时没问题,但当我使用seek跳到某个位置然后用read(1)读取时,就出现了这个错误。
1 个回答
2
在文件中查找时,移动的是读取指针,单位是字节,而不是字符。而.read()
这个调用是希望能够读取完整的字符。因为UTF-8编码对于ASCII字符集以外的任何unicode字符,都会使用多个字节,所以你不能随便在一个多字节的UTF-8字符中间查找,然后期待.read()
能够正常工作。
比如,U+00a4这个字符(就是ä
)是用两个字节来表示的,分别是C3和A4。在文件中,这意味着现在有5个字节,分别是s
、p
、十六进制字节C3和A4,最后是m
。
如果你把指针移动到位置3,那么指针就指向了A4这个字节。接着调用.read()
就会失败,因为没有前面的C3字节,就没有足够的信息来解码这个字符。这会引发UnicodeDecodeError
错误;因为A4这个字节是不被期待的,它不是一个有效的UTF-8序列。
不如把指针移动到位置4:
>>> f.seek(3); f.read(1)
'm'
更好的办法是,不要在UTF-8数据中随便查找,或者以二进制模式打开文件,然后手动解码。