用Python安全读取UTF-8文件

Last Updated: 2023-05-18 09:32:16 Thursday

-- TOC --

UTF-8编码格式的文件已经几乎统一天下,但是Python在读取的时候,还是有可能遇到意外。

问题是什么?

用Python读取UTF-8文本文件很简单,一行代码就搞定,如下:

with open(filename) as f:
    ...

但这样写的代码有个问题,在调用open函数的时候,没有指定encoding参数。为什么要指定encoding参数?默认难道不是UTF-8吗?

是的,不一定!在Linux平台下,open函数的encoding参数基本上默认都是UTF-8,但在Windows平台下就不一定了。在读取文本文件的时候,如果open()函数没有指定encoding,python3会选取代码所运行的计算机操作系统的默认编码作为open()函数的编码方式。

当使用非UTF-8的方式读取UTF-8文件时,可能的错误是这样的:

UnicodeDecodeError: \
'gbk' codec can't decode byte 0xae in position 54: illegal multibyte sequence
...
UnicodeDecodeError: \
'utf-8' codec can't decode byte 0xb5 in position 2: invalid start byte

用Python查看系统默认charset:

$ python3 -q  # Ubuntu
>>> import locale
>>> locale.getpreferredencoding(False)
'UTF-8'
C:\Users\xinlin>python -q  # Win10
>>> import locale
>>> locale.getpreferredencoding(False)
'cp936'

另一个接口:sys.getfilesystemencoding(),用来查看OS文件系统所使用的encoding,即用什么编码方式,将unicode的文件名转换成OS支持的文件名。Window系统下,这个接口返回的是UTF-8。

为什么建议给open函数加上encoding参数?

如上所述,open函数没有设置encoding时,会使用OS的默认编码,这就带来了不确定性。例如Windows中文系统,默认是GBK编码;而繁体中文的Windows系统,默认是Big5编码。

如果代码读取的文件是UTF-8编码,为了让你的Python程序能够有一定的跨平台运行的能力,建议给open函数统一加上encoding='utf-8'

with open(filename, encoding='utf-8') as f:
    ...

这样做还规避了另外一个蛋疼的问题:如果不加encoding='utf-8',你的程序在Windows系统下运行,在读取英文UTF-8文件时很正常,而在读取含有中文的UTF-8文件时,就崩溃!

如果你的程序只读取英文文档,那很OK,但也要意识到,增加中文文档的读取功能,很可能是增加了一个新的feature...

用rb方式读取时

用rb方式读取文件时,就没有必要考虑encoding的问题了。

with open(filename, 'rb') as f:
    ...

因为这个时候,代码其实是不关心文件内容的编码是什么的,统一按照byte的方式读取和处理。

有可能遇到非utf-8编码的文件

是的,虽然也许你很确定你的程序要读的文件,都是utf-8格式的,但是一旦不小心出现非utf-8编码的文件,上面的代码也存在崩溃的风险。更安全的代码写法是:

with open(filename, encoding='utf-8') as f:
    try:
        ...
    except:
        ...

指定按utf-8的方式读取文件,但是如果文件不是utf-8,抛出异常。注意,异常不是在调用open的时候抛出,而是在后续read的时候抛出。

Python官方给出的其它方案

You can use the Python UTF-8 Mode to change the default text encoding to UTF-8. You can enable the Python UTF-8 Mode via the -X utf8 command line option, or the PYTHONUTF8=1 environment variable.

在启动Python的时候,使用命令行参数-X utf8;或者设置环境变量PYTHONUTF8=1。

本文链接:https://cs.pynote.net/sf/python/202109191/

-- EOF --

-- MORE --