能否通过简易try-except块安全读取UTF-8与Latin1文件?
我提出假设:所有合法的Latin1字符要么能被Python的UTF-8编码器正确解析,要么会抛出错误。因此若仅处理UTF-8或Latin1文件,可通过如下带try-except的代码安全读取,不会出现Mojibake:
from pathlib import Path def read_utf8_or_latin1_text(path: Path, *args, **kwargs): try: return path.read_text(encoding="utf-8") except UnicodeDecodeError: return path.read_text(encoding="latin1")
我在大型字符数据集上测试该假设:单个Latin1字符测试时无问题,但测试Latin1字符序列时,生成了1920例UTF-8 Mojibake,构成反例。现问询:该方法是否始终能安全读取两类文件?
回答
这个方法不能始终安全读取UTF-8和Latin1文件,你的测试结果已经验证了这一点。
核心问题在于:部分Latin1字符的字节组合恰好是合法的UTF-8编码序列,此时Python的UTF-8解码器不会抛出UnicodeDecodeError,会直接将这些字节解码成与原Latin1字符完全不同的Unicode字符,也就是你遇到的Mojibake。
举个典型例子:Latin1中的字节0xC0(对应字符À)和0x80(对应控制字符)组合,在UTF-8中会被解码为U+0000(空字符),但原内容是两个独立的Latin1字符。这种情况下,代码会错误地用UTF-8解码,完全偏离了原文件的真实内容。
之所以单个Latin1字符测试没问题,是因为单个Latin1字节(0x00-0xFF)中,只有0x80-0xFF范围内的字节作为单独字节时不是合法UTF-8(UTF-8单字节只能是0x00-0x7F),所以单个字符测试时,非ASCII的Latin1字符会触发UnicodeDecodeError,进而走Latin1分支。但当这些字节组成符合UTF-8多字节规则的序列时,就会被错误解码。
综上,这种"先UTF-8尝试,失败再用Latin1"的逻辑,无法覆盖所有Latin1文件的情况,存在产生乱码的风险。
内容的提问来源于stack exchange,提问作者Simon Streicher

