使用seek()和read()时出现UnicodeDecodeError

0 投票
1 回答
838 浏览
提问于 2025-04-17 15:43

我正在跟着《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个字节,分别是sp、十六进制字节C3和A4,最后是m

如果你把指针移动到位置3,那么指针就指向了A4这个字节。接着调用.read()就会失败,因为没有前面的C3字节,就没有足够的信息来解码这个字符。这会引发UnicodeDecodeError错误;因为A4这个字节是不被期待的,它不是一个有效的UTF-8序列。

不如把指针移动到位置4:

>>> f.seek(3); f.read(1)
'm'

更好的办法是,不要在UTF-8数据中随便查找,或者以二进制模式打开文件,然后手动解码。

撰写回答