Linux乱码,python3中文乱码问题


1. 问题现象

最近有一python项目要从2.x版升级到python3.6; 我也示先了解了py2和py3升级要处理的问题(py3字符串默认unicode,字符串引号前的u不再需要,异常catch时语法变化,import不再相对异过等等); 升级完成之后,程序跑起来了,但在有一接收中文参数语句发生了异常。

    SyntaxError: 'ascii' codec can't decode byte 0xe6 in position 1:  ordinal not

    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 1

更奇怪的是这问题只发生在线上docker容器里,本地调试(windows)确没事;本来想着在本地重现好定位的,发现不行。

2. 排查过程

2.1 网上找答案

先去baidu找答案,看到的基本都是说python默认编码问题和文件编码问题,

# python2.x
import sys
reload(sys)
sys.setdefaultencoding('utf8')

文件改成utf8编码,并修改py文件头
# -*- coding: utf-8 -*-
但我这是python3啊,sys.setdefaultencoding不需要了,py3用下面

import pickle
f = open(file, 'rb')
dict = pickle.load(f,encoding='utf-8')

以上方法都试了无果,可能有部分人按照以上处理已解决了,那恭喜你。

2.2 用python3交互调试

那网上方法都失灵只能靠自己了,自己写demo调总可以吧。
在我docker的linux上测试几个简单的命令得到如下结果,有经验的人可能就已知道该如何解决了。
python errencode

>>> import sys
>>> sys.getfilesystemencoding()
'ascii'
>>> sys.getdefaultencoding()
'utf-8'

奇怪吧,原来最简单的语句在这环境下居然出问题,python语法确定是没问题的嘛,入门的都知道;那么问题就出在这个环境。

3. 问题解决

经排查都清楚是外部环境问题了,那么如何解决呢, 其实这里影响到python异常的原因是linux环境的本地化(locale)设置,最最根本的直白的原因是LC_CTYPE环境变量没配置正确,后面进一步讲解,这里最直接的解决办法是export LC_CTYPE=C.UTF-8,然后再运行你的python程序。
解决思路

想永久性解决(下次linux重启后可用)
在/etc/prifile加入

export  LANG=C.UTF-8
export  LC_ALL=C.UTF-8

4. 问题引申 – 本地化和字符集编码

4.1 字符集编码

字符集简而言之系一堆字符的集合,我们为区分各个字符,给予一个数字的编号与字符对应,这谓之字符集编码; 譬如,

ascii字符集里, 'a'  编码为  97 ,       'b'  编码为 98
gbk字符集里,   '广' 编码为 24191 ,     '东' 编码为 19996 

Unicode字符集就是为了统一所有文字的编码应运而生。Unicode把所有语言都统一到一套编码里,这样就可以在只一文章里显示各种地区的文字了;Unicode用两个字节表示一个字符,总共可表示6万多个字符(先不考虑有些4字节的生字),足够全球通用。
UTF-8是一种面向英文内容更加节省存储空间的一种变长编码方案,英文字母数字出现频率高用一个字节编码,中文汉字和日本字等出现频率低用3个字节编码,越低频率出现的字符用越多字节编码。
计算机程序处理字符只认识字符编码的数字二进制形式,在python3程序中,内存保存的字符串数据x="hello"属str类型,是unicode的形式两个字节作一个字符;如果x要串行化(用于传输或持久化成文件),那么需要选择一种编码方案对x里字符转化为字节存储,这过程叫encode;反之,用文件或网络传过来的字节流中转为内存变量存储一个个字符,称之decode

'unicode字符串'  encode()->  b'unicode\xe5\xad\x97\xe7\xac\xa6\xe4\xb8\xb2'
b'unicode\xe5\xad\x97\xe7\xac\xa6\xe4\xb8\xb2' decode()-> unicode字符串'

####### example ########
>>> x = '大家good'
>>> x.encode('utf-8')
b'\xe5\xa4\xa7\xe5\xae\xb6good'
>>> x.encode('gbk')
b'\xb4\xf3\xbc\xd2good'
>>> 'unicode字符串'.encode()
b'unicode\xe5\xad\x97\xe7\xac\xa6\xe4\xb8\xb2'

4.2 Linux本地化

Linux的世界里有个locale的命令,它展示了与本系统环境相关的配置,特别是语言,还包括时间显示方式,地址,电话号码格式等等;全世界都在使用centos7,各个地区间都有习惯,偏好之类的不同,这就是通过locale定义;
locale本质就是LC_开头的10多个环境变量,设置locale本质就是设置环境变量,先看一个locale命令显示结果(每个人的可能都不一样)

[root@cppcloud ~]# locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"          #字符编码相关,所有字符的处理方式
LC_NUMERIC="en_US.UTF-8"        #格式化非货币的数字显示
LC_TIME="en_US.UTF-8"           #格式化时间和日期
LC_COLLATE="en_US.UTF-8"        #排序方式
LC_MONETARY="en_US.UTF-8"       #货币
LC_MESSAGES="en_US.UTF-8"       #提示消息,警告,错误消息显示方式
LC_PAPER="en_US.UTF-8"          #默认纸强大小
LC_NAME="en_US.UTF-8"           #姓名格式
LC_ADDRESS="en_US.UTF-8"        #地址格式
LC_TELEPHONE="en_US.UTF-8"      #电话号码格式
LC_MEASUREMENT="en_US.UTF-8"    #度量衡表达方式
LC_IDENTIFICATION="en_US.UTF-8" #locale概述
LC_ALL=
环境变量的含义

上面有注释的LC_环境变量就不重复了;
LANG不代表具体类形格式,它的值是在当某个LC_
变量没有设置时所使用的默认值;如果你不相为那么多LC_变量一一定义相同的值,那么就用LANG。

LC_ALL是一个批量设置LC_*的宏,对LC_ALL赋值相当于给一个个LC_XXX设置了值。

locale值规则

en_US.UTF-8是什么意思呢,我改成zh.gbk可不可以?

locale 命名规则:<语言>_<地区>.<字符集编码>@修正值

此处代表语言是英文,地区是美国,utf-8字符集编码; 另外中文用zh,中国用CN;
注意不是别人机上的值你都可以照抄的,先要看你机上安装了这种locale未,查看可用的locale值如下。

> locale -a
C  # 不支持中文,默认ascii编码
C.UTF-8
en_US.UTF-8
POSIX   # 是C的别名

要设置locale,一定要设置成环境变量即export,否则不会传递给子进程;要永久生效就在/etc/profile下设置就可以。

# /etc/profile
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

4.3 乱码总结

在非纯英文环境难免要处理到编码解码相关问题,字符显示从头到尾所经由的环节之多,其中一个编码有错就会出现乱码。当你面前出现乱码时检查下面几个环节逐个排查:

4.3.1 文件自身编码

拿python源文件为例,当你文件头部声明了# -*- coding: utf-8 -*-时,文件内容又有非英文字符,一定要确保文件保存时使用utf-8无BOM编码;如果你的编辑器没法显示编码方式时,可以使用Notepad++编辑,有个视图的菜单一目了然了。

4.3.2 终端显示编码

经常看到同一文件在xshell下显示正常,在putty下显示却乱码,那就要设置一个终端编码方案了。

4.3.3 应用软件的编码设置

这要看具体程序了,如果python3的话,就sys.getdefaultencoding()显示默认编码;vi/vim的话,输入命令:set encode可以查看。有的应用应用使用的编码会根据Locale环境变量,vim就如是。

4.3.4 Locale环境

本文上面遇到的问题就是属于这类;应用是python3.6,使用docker ubuntu,没做任何设置的情况下,LC_CTYPE=POSIX,对应的文件系统编码是ascii; python3下可以通过sys.getfilesystemencoding()查看。

/latefirstcmt/8