一、背景说明

最开始不愿意使用Python,一大原因是因为Python2默认使用ASCII编码处理中文可以说是一件痛苦的事情。仅从更换默认编码一项变换,就可以说Python3和Python2不算同一门语言。

Python3更换为默认使用Unicode(utf-8)编码,一直使用下来再没有遇到编码问题带来的困挠,似乎编码问题在Python3时代就该完全消失的。但这两天遇到了一个问题。

在调用一个库时,出现了一个异常报错类似如UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128),几经排查之下发现只要该库返回结果包含中文,我这边使用print()打印该结果时就会出现该异常。

 

二、原因分析

2.1 数据要经过编码才能传输

我们知道数据在网络上传输时,需要先编码;平时我们可能并不注意,但现在要明确,编码的原因不在于网络而在于传输。

print()相当于把字符串从内存传输到了tty上,所以print()是需要encode()动作的;平时我们print()时一般都不需要encode(),只是因为当print()检测到传来的参数是不是byte类型时自动进行了编码。

 

2.2 print()使用何种编码

Python3默认使用的是utf-8,这可以通过sys.getdefaultencoding()进行确认。但这只是默认,当系统配置了LC_ALL、LC_CTYPE、LANG等环境变量时(三者优先级从高到低),Python3采用这些变量配置的编码;如果这些变量配置的是utf-8那Python3用的就还是utf-8,但如果不是utf-8那Python3所用的也就不是utf-8了。

当前使用的编码可通过sys.getfilesystemencoding()获取。

 

三、场景复现

为简单起见,我们这里直接以打印一个中文字符串作为演示,示例代码如下(我这里保存成test_encode.py):

import sys

class TestEncode():
    def __int__(self):
        pass

    def main_logic(self):
        # 打印语言默认编码
        print(f"defaultencoding--{sys.getdefaultencoding()}")
        # 打印系统配置的编码
        print(f"filesystemencoding--{sys.getfilesystemencoding()}")

        # 最后尝试打印中文
        print("中文")

if __name__ == "__main__":
    obj = TestEncode()
    obj.main_logic()

shell依次执行如下命令:

# 查看当前编码情况
locale
# 确认在utf-8情况下打印中文无误
python3 test_encode.py

# 设置LC_TYPE,C代表ASCII
export LC_CTYPE="C"
# 查看当前编码情况
locale
# 再次运行,确认系统编码已改变,并出现编码错误
python3 test_encode.py

最终结果如下,在系统编码配置为utf-8时打印正常,在系统编码改为C(即ASCII)后打印报编码异常(不过我在root用户环境修改编码一直不成功,不懂我电脑有点问题还是什么原因):

ISO python3 编码 python3ascii编码报错_环境变量

 

四、解决办法

其实追根究底,打印报错本质原因就是标准输出的编码不支持要打印的字符,对中文而言就是不是utf8(当然要说的话还可以是gbk这些),那解决办法就是去把标准输出的编码给设成utf8就完事了。

那各语言的标准输出编码由什么决定呢,一般是语言底层根据一些自己的变量去设置标准输出(python是PYTHONENCODING等几个),如果没有那就取系统的LC_ALL等配置编码的值去设置标准输出。

那基于这个事实,我们就有了以下三种解决办法。

方法一:自己临时自行修改标准输出为utf-8

这种方法最保险,能确认自己不被覆盖;但如果是一个项目那得确保在一开始设置,不然每次print前设置一次也很麻烦。

import sys
import codecs

sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
print("中文")

方法二:设置语言会采用的的环境变量PYTHONIOENCODING

这种方法按理感觉有时有bug,该环境变量在一些解析器中没被采用。

PYTHONIOENCODING=utf-8 python test_encode.py

方法三:设置语言会采用的系统环境变量值为utf-8

这种方法各种语言应该都通用,但设环境变量的方式和位置多样只要在脚本执行前进行设置就行。如最简单的,在/etc/profile中追加如下行:

# 优先级 LC_ALL > LC_*(包括决定月分显示语言的LC_TIME等)> LANG
export LC_ALL="en_US.utf8"

如果配了变量仍有报错,那一般都是运行的代码没正确读取变量所致,可以在脚本中获取下LC_ALL这变量值确认,python是os.environ。 

 

参考:

https://timothyqiu.com/archives/surrogateescape-in-python-3/